diff options
232 files changed, 12877 insertions, 4034 deletions
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..366946e8 100644 --- a/.github/mailmap +++ b/.github/mailmap | |||
@@ -1,13 +1,17 @@ | |||
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> |
5 | kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com> | 6 | kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com> |
7 | kalvn <kalvnthereal@gmail.com> <kalvn@pm.me> | ||
8 | Neros <contact@neros.fr> <NerosTie@users.noreply.github.com> | ||
6 | Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm | 9 | Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm |
7 | Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar> | 10 | Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar> |
8 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com> | 11 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com> |
9 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com> | 12 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com> |
10 | Sébastien Sauvage <sebsauvage@sebsauvage.net> | 13 | Sébastien Sauvage <sebsauvage@sebsauvage.net> |
14 | Sébastien NOBILI <code@pipoprods.org> <s-code-github@pipoprods.org> | ||
11 | Timo Van Neerden <fire@lehollandaisvolant.net> | 15 | Timo Van Neerden <fire@lehollandaisvolant.net> |
12 | Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com> | 16 | Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com> |
13 | VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com> | 17 | VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com> |
diff --git a/.travis.yml b/.travis.yml index f466c317..d04a45d1 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -1,5 +1,4 @@ | |||
1 | sudo: false | 1 | dist: bionic |
2 | dist: trusty | ||
3 | 2 | ||
4 | matrix: | 3 | matrix: |
5 | include: | 4 | include: |
@@ -1,6 +1,6 @@ | |||
1 | 782 ArthurHoaro <arthur@hoa.ro> | 1 | 903 ArthurHoaro <arthur@hoa.ro> |
2 | 401 VirtualTam <virtualtam@flibidi.net> | 2 | 402 VirtualTam <virtualtam@flibidi.net> |
3 | 218 nodiscc <nodiscc@gmail.com> | 3 | 250 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> |
@@ -8,11 +8,12 @@ | |||
8 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> | 8 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> |
9 | 9 Willi Eggeling <thewilli@gmail.com> | 9 | 9 Willi Eggeling <thewilli@gmail.com> |
10 | 8 Christophe HENRY <christophe.henry@sbgodin.fr> | 10 | 8 Christophe HENRY <christophe.henry@sbgodin.fr> |
11 | 7 Lucas Cimon <lucas.cimon@gmail.com> | ||
11 | 6 B. van Berkum <dev@dotmpe.com> | 12 | 6 B. van Berkum <dev@dotmpe.com> |
13 | 6 kalvn <kalvnthereal@gmail.com> | ||
12 | 6 llune <llune@users.noreply.github.com> | 14 | 6 llune <llune@users.noreply.github.com> |
13 | 5 Lucas Cimon <lucas.cimon@gmail.com> | ||
14 | 5 Mark Schmitz <kramred@gmail.com> | 15 | 5 Mark Schmitz <kramred@gmail.com> |
15 | 5 kalvn <kalvnthereal@gmail.com> | 16 | 5 Sébastien NOBILI <code@pipoprods.org> |
16 | 4 Alexandre Alapetite <alexandre@alapetite.fr> | 17 | 4 Alexandre Alapetite <alexandre@alapetite.fr> |
17 | 4 David Sferruzza <david.sferruzza@gmail.com> | 18 | 4 David Sferruzza <david.sferruzza@gmail.com> |
18 | 4 Immánuel Fodor <immanuelfactor+github@gmail.com> | 19 | 4 Immánuel Fodor <immanuelfactor+github@gmail.com> |
@@ -21,26 +22,33 @@ | |||
21 | 2 Alexandre G.-Raymond <alex@ndre.gr> | 22 | 2 Alexandre G.-Raymond <alex@ndre.gr> |
22 | 2 Chris Kuethe <chris.kuethe@gmail.com> | 23 | 2 Chris Kuethe <chris.kuethe@gmail.com> |
23 | 2 Felix Bartels <felix@host-consultants.de> | 24 | 2 Felix Bartels <felix@host-consultants.de> |
25 | 2 Guillaume Virlet <github@virlet.org> | ||
24 | 2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org> | 26 | 2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org> |
25 | 2 Mathieu Chabanon <git@matchab.fr> | 27 | 2 Mathieu Chabanon <git@matchab.fr> |
26 | 2 Miloš Jovanović <mjovanovic@gmail.com> | 28 | 2 Miloš Jovanović <mjovanovic@gmail.com> |
29 | 2 Neros <contact@neros.fr> | ||
27 | 2 Qwerty <champlywood@free.fr> | 30 | 2 Qwerty <champlywood@free.fr> |
28 | 2 Stephen Muth <smuth4@gmail.com> | 31 | 2 Stephen Muth <smuth4@gmail.com> |
29 | 2 Timo Van Neerden <fire@lehollandaisvolant.net> | 32 | 2 Timo Van Neerden <fire@lehollandaisvolant.net> |
33 | 2 dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> | ||
34 | 2 flow.gunso <flow.gunso@gmail.com> | ||
30 | 2 julienCXX <software@chmodplusx.eu> | 35 | 2 julienCXX <software@chmodplusx.eu> |
31 | 2 philipp-r <philipp-r@users.noreply.github.com> | 36 | 2 philipp-r <philipp-r@users.noreply.github.com> |
32 | 2 pips <pips@e5150.fr> | 37 | 2 pips <pips@e5150.fr> |
33 | 2 trailjeep <trailjeep@gmail.com> | 38 | 2 trailjeep <trailjeep@gmail.com> |
39 | 2 yude <yudesleepy@gmail.com> | ||
34 | 1 Adrien Oliva <adrien.oliva@yapbreak.fr> | 40 | 1 Adrien Oliva <adrien.oliva@yapbreak.fr> |
35 | 1 Adrien le Maire <adrien@alemaire.be> | 41 | 1 Adrien le Maire <adrien@alemaire.be> |
36 | 1 Alexis J <alexis@effingo.be> | 42 | 1 Alexis J <alexis@effingo.be> |
37 | 1 Angristan <angristan@users.noreply.github.com> | 43 | 1 Angristan <angristan@users.noreply.github.com> |
38 | 1 Bish Erbas <42714627+bisherbas@users.noreply.github.com> | 44 | 1 Bish Erbas <42714627+bisherbas@users.noreply.github.com> |
39 | 1 BoboTiG <bobotig@gmail.com> | 45 | 1 BoboTiG <bobotig@gmail.com> |
46 | 1 Brendan M. Sleight <bms.git@barwap.com> | ||
40 | 1 Bronco <bronco@warriordudimanche.net> | 47 | 1 Bronco <bronco@warriordudimanche.net> |
41 | 1 Buster One <37770318+buster-one@users.noreply.github.com> | 48 | 1 Buster One <37770318+buster-one@users.noreply.github.com> |
42 | 1 D Low <daniellowtw@gmail.com> | 49 | 1 D Low <daniellowtw@gmail.com> |
43 | 1 Daniel Jakots <vigdis@chown.me> | 50 | 1 Daniel Jakots <vigdis@chown.me> |
51 | 1 David Foucher <dev@tyjak.net> | ||
44 | 1 Dennis Verspuij <dennisverspuij@users.noreply.github.com> | 52 | 1 Dennis Verspuij <dennisverspuij@users.noreply.github.com> |
45 | 1 Dimtion <zizou.xena@gmail.com> | 53 | 1 Dimtion <zizou.xena@gmail.com> |
46 | 1 Fanch <fanch-github@qth.fr> | 54 | 1 Fanch <fanch-github@qth.fr> |
@@ -48,20 +56,23 @@ | |||
48 | 1 Florian Voigt <flvoigt@me.com> | 56 | 1 Florian Voigt <flvoigt@me.com> |
49 | 1 Franck Kerbiriou <FranckKe@users.noreply.github.com> | 57 | 1 Franck Kerbiriou <FranckKe@users.noreply.github.com> |
50 | 1 Gary Marigliano <gmarigliano93@gmail.com> | 58 | 1 Gary Marigliano <gmarigliano93@gmail.com> |
51 | 1 Guillaume Virlet <github@virlet.org> | ||
52 | 1 Jonathan Amiez <jonathan.amiez@gmail.com> | 59 | 1 Jonathan Amiez <jonathan.amiez@gmail.com> |
53 | 1 Jonathan Druart <jonathan.druart@gmail.com> | 60 | 1 Jonathan Druart <jonathan.druart@gmail.com> |
54 | 1 Julien Pivotto <roidelapluie@inuits.eu> | 61 | 1 Julien Pivotto <roidelapluie@inuits.eu> |
55 | 1 Kevin Canévet <kevin@streamroot.io> | 62 | 1 Kevin Canévet <kevin@streamroot.io> |
63 | 1 Kevin Masson <kevin.masson@methodinthemadness.eu> | ||
56 | 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org> | 64 | 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org> |
57 | 1 Lionel Martin <renarddesmers@gmail.com> | 65 | 1 Lionel Martin <renarddesmers@gmail.com> |
58 | 1 Mark Gerarts <mark.gerarts@gmail.com> | 66 | 1 Mark Gerarts <mark.gerarts@gmail.com> |
59 | 1 Marsup <marsup@gmail.com> | 67 | 1 Marsup <marsup@gmail.com> |
60 | 1 Neros <contact@neros.fr> | 68 | 1 Paul van den Burg <github@paulvandenburg.nl> |
61 | 1 Rajat Hans <rajathans9@gmail.com> | 69 | 1 Rajat Hans <rajathans9@gmail.com> |
62 | 1 Sbgodin <Sbgodin@users.noreply.github.com> | 70 | 1 Sbgodin <Sbgodin@users.noreply.github.com> |
71 | 1 Sebastien Wains <sebw@users.noreply.github.com> | ||
63 | 1 TsT <tst2005@gmail.com> | 72 | 1 TsT <tst2005@gmail.com> |
64 | 1 agentcobra <agentcobra@free.fr> | 73 | 1 agentcobra <agentcobra@free.fr> |
74 | 1 aguy <aguytech@users.noreply.github.com> | ||
65 | 1 dimtion <zizou.xena@gmail.com> | 75 | 1 dimtion <zizou.xena@gmail.com> |
66 | 1 durcheinandr <jochen@durcheinandr.de> | 76 | 1 durcheinandr <jochen@durcheinandr.de> |
67 | 1 lapineige <lapineige@users.noreply.github.com> | 77 | 1 lapineige <lapineige@users.noreply.github.com> |
78 | 1 rfolo9li <50079896+rfolo9li@users.noreply.github.com> | ||
diff --git a/CHANGELOG.md b/CHANGELOG.md index abf802ea..4bae5b48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -4,6 +4,66 @@ 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.0](https://github.com/shaarli/Shaarli/releases/tag/v0.12.0-beta) - UNRELEASED [beta 2020-08-27] | ||
8 | |||
9 | **Save you `data/` folder before updating!** | ||
10 | |||
11 | This is a beta version containing major changes, including new URLs for Shaarli and datastore format update. | ||
12 | Be aware that by using a beta version you might encounter bugs, and that 3rd party themes or plugins might not be compatible. | ||
13 | |||
14 | ### Added | ||
15 | - Thumbnailer: add soundcloud.com to list of common media domains | ||
16 | - Markdown rendering is now integrated into Shaarli core | ||
17 | - Add autofocus on tag cloud filter input | ||
18 | - Japanese translations | ||
19 | - Support for local anchor URL (startting with `#`) | ||
20 | - LDAP authentication | ||
21 | - Encapsulated PageCacheManager | ||
22 | - Docs: | ||
23 | - add screenshots of all pages | ||
24 | - section about mkdocs | ||
25 | - Ulauncher extension | ||
26 | - CI: run against PHP 7.4 | ||
27 | |||
28 | ### Changed | ||
29 | - Introduce Bookmark object and Service layer | ||
30 | - Save bookmark as objects in the datastore | ||
31 | - Handle bookmark as objects across the whole codebase (except templates and plugins) | ||
32 | - Process all Shaarli page through Slim controller, with proper URL rewriting (see #1516) | ||
33 | - ATOM feed: use instance name as author name instead of URL | ||
34 | - Updated French translation | ||
35 | - Docs: | ||
36 | - Troubleshooting page rewritten | ||
37 | - Updated unit tests page | ||
38 | - Updated Server security page | ||
39 | |||
40 | ### Fixed | ||
41 | - Undefined index: thumbnail in daily page | ||
42 | - Undefined index: thumbnail on OpenGraph headers | ||
43 | - Undefined index: updated on linklist | ||
44 | - Make sure that bookmark sort is consistent, even with equal timestamps | ||
45 | - Code PHP version check as requirement bumped to PHP 7.1 | ||
46 | - Thumbnail images lazy loading | ||
47 | - Markdown plugin: fix RSS feed direct link reverse | ||
48 | - Fix RSS permalink included in Markdown bloc | ||
49 | - Demo plugin: multiple typos | ||
50 | - Makefile target for releases | ||
51 | - Makefile target for html documentation | ||
52 | - Session cookie setting being set while session is active | ||
53 | - Deprecated use of implode | ||
54 | - Division by zero in tag cloud | ||
55 | - CI: deprecated linux distribution and sudo directive | ||
56 | - Docker build: gcc is no longer included in python alpine image | ||
57 | - Docs: | ||
58 | - Outdated Docker documentation for stable branch | ||
59 | - Outdated links | ||
60 | - Plugin description in meta files | ||
61 | |||
62 | ### Removed | ||
63 | - Markdown plugin | ||
64 | - Docs: | ||
65 | - emojione & twemoji removed | ||
66 | |||
7 | ## [v0.11.1](https://github.com/shaarli/Shaarli/releases/tag/v0.11.1) - 2019-08-03 | 67 | ## [v0.11.1](https://github.com/shaarli/Shaarli/releases/tag/v0.11.1) - 2019-08-03 |
8 | 68 | ||
9 | Release to fix broken Docker build on the latest version. | 69 | 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/application/Thumbnailer.php b/application/Thumbnailer.php index 314baf0d..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 | /** |
@@ -90,7 +89,7 @@ class Thumbnailer | |||
90 | 89 | ||
91 | try { | 90 | try { |
92 | return $this->wt->thumbnail($url); | 91 | return $this->wt->thumbnail($url); |
93 | } catch (WebThumbnailerException $e) { | 92 | } catch (\Throwable $e) { |
94 | // Exceptions are only thrown in debug mode. | 93 | // Exceptions are only thrown in debug mode. |
95 | error_log(get_class($e) . ': ' . $e->getMessage()); | 94 | error_log(get_class($e) . ': ' . $e->getMessage()); |
96 | } | 95 | } |
diff --git a/application/Utils.php b/application/Utils.php index 4b7fc546..9c9eaaa2 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -87,10 +87,14 @@ 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 (null === $input) { | ||
95 | return null; | ||
96 | } | ||
97 | |||
94 | if (is_bool($input)) { | 98 | if (is_bool($input)) { |
95 | return $input; | 99 | return $input; |
96 | } | 100 | } |
@@ -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 4745ac94..09ce6445 100644 --- a/application/api/ApiMiddleware.php +++ b/application/api/ApiMiddleware.php | |||
@@ -71,7 +71,14 @@ class ApiMiddleware | |||
71 | $response = $e->getApiResponse(); | 71 | $response = $e->getApiResponse(); |
72 | } | 72 | } |
73 | 73 | ||
74 | 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 | ; | ||
75 | } | 82 | } |
76 | 83 | ||
77 | /** | 84 | /** |
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php index 5156a5f7..faebb8f5 100644 --- a/application/api/ApiUtils.php +++ b/application/api/ApiUtils.php | |||
@@ -67,7 +67,7 @@ class ApiUtils | |||
67 | if (! $bookmark->isNote()) { | 67 | if (! $bookmark->isNote()) { |
68 | $out['url'] = $bookmark->getUrl(); | 68 | $out['url'] = $bookmark->getUrl(); |
69 | } else { | 69 | } else { |
70 | $out['url'] = $indexUrl . $bookmark->getUrl(); | 70 | $out['url'] = rtrim($indexUrl, '/') . '/' . ltrim($bookmark->getUrl(), '/'); |
71 | } | 71 | } |
72 | $out['shorturl'] = $bookmark->getShortUrl(); | 72 | $out['shorturl'] = $bookmark->getShortUrl(); |
73 | $out['title'] = $bookmark->getTitle(); | 73 | $out['title'] = $bookmark->getTitle(); |
diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php index f9b21d3d..1beb8be2 100644 --- a/application/bookmark/Bookmark.php +++ b/application/bookmark/Bookmark.php | |||
@@ -3,6 +3,7 @@ | |||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Bookmark; |
4 | 4 | ||
5 | use DateTime; | 5 | use DateTime; |
6 | use DateTimeInterface; | ||
6 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; | 7 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; |
7 | 8 | ||
8 | /** | 9 | /** |
@@ -36,16 +37,16 @@ class Bookmark | |||
36 | /** @var array List of bookmark's tags */ | 37 | /** @var array List of bookmark's tags */ |
37 | protected $tags; | 38 | protected $tags; |
38 | 39 | ||
39 | /** @var string Thumbnail's URL - false if no thumbnail could be found */ | 40 | /** @var string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found */ |
40 | protected $thumbnail; | 41 | protected $thumbnail; |
41 | 42 | ||
42 | /** @var bool Set to true if the bookmark is set as sticky */ | 43 | /** @var bool Set to true if the bookmark is set as sticky */ |
43 | protected $sticky; | 44 | protected $sticky; |
44 | 45 | ||
45 | /** @var DateTime Creation datetime */ | 46 | /** @var DateTimeInterface Creation datetime */ |
46 | protected $created; | 47 | protected $created; |
47 | 48 | ||
48 | /** @var DateTime Update datetime */ | 49 | /** @var DateTimeInterface datetime */ |
49 | protected $updated; | 50 | protected $updated; |
50 | 51 | ||
51 | /** @var bool True if the bookmark can only be seen while logged in */ | 52 | /** @var bool True if the bookmark can only be seen while logged in */ |
@@ -100,12 +101,12 @@ class Bookmark | |||
100 | || ! is_int($this->id) | 101 | || ! is_int($this->id) |
101 | || empty($this->shortUrl) | 102 | || empty($this->shortUrl) |
102 | || empty($this->created) | 103 | || empty($this->created) |
103 | || ! $this->created instanceof DateTime | 104 | || ! $this->created instanceof DateTimeInterface |
104 | ) { | 105 | ) { |
105 | throw new InvalidBookmarkException($this); | 106 | throw new InvalidBookmarkException($this); |
106 | } | 107 | } |
107 | if (empty($this->url)) { | 108 | if (empty($this->url)) { |
108 | $this->url = '?'. $this->shortUrl; | 109 | $this->url = '/shaare/'. $this->shortUrl; |
109 | } | 110 | } |
110 | if (empty($this->title)) { | 111 | if (empty($this->title)) { |
111 | $this->title = $this->url; | 112 | $this->title = $this->url; |
@@ -188,7 +189,7 @@ class Bookmark | |||
188 | /** | 189 | /** |
189 | * Get the Created. | 190 | * Get the Created. |
190 | * | 191 | * |
191 | * @return DateTime | 192 | * @return DateTimeInterface |
192 | */ | 193 | */ |
193 | public function getCreated() | 194 | public function getCreated() |
194 | { | 195 | { |
@@ -198,7 +199,7 @@ class Bookmark | |||
198 | /** | 199 | /** |
199 | * Get the Updated. | 200 | * Get the Updated. |
200 | * | 201 | * |
201 | * @return DateTime | 202 | * @return DateTimeInterface |
202 | */ | 203 | */ |
203 | public function getUpdated() | 204 | public function getUpdated() |
204 | { | 205 | { |
@@ -270,7 +271,7 @@ class Bookmark | |||
270 | * Set the Created. | 271 | * Set the Created. |
271 | * Note: you shouldn't set this manually except for special cases (like bookmark import) | 272 | * Note: you shouldn't set this manually except for special cases (like bookmark import) |
272 | * | 273 | * |
273 | * @param DateTime $created | 274 | * @param DateTimeInterface $created |
274 | * | 275 | * |
275 | * @return Bookmark | 276 | * @return Bookmark |
276 | */ | 277 | */ |
@@ -284,7 +285,7 @@ class Bookmark | |||
284 | /** | 285 | /** |
285 | * Set the Updated. | 286 | * Set the Updated. |
286 | * | 287 | * |
287 | * @param DateTime $updated | 288 | * @param DateTimeInterface $updated |
288 | * | 289 | * |
289 | * @return Bookmark | 290 | * @return Bookmark |
290 | */ | 291 | */ |
@@ -346,7 +347,7 @@ class Bookmark | |||
346 | /** | 347 | /** |
347 | * Get the Thumbnail. | 348 | * Get the Thumbnail. |
348 | * | 349 | * |
349 | * @return string|bool | 350 | * @return string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found |
350 | */ | 351 | */ |
351 | public function getThumbnail() | 352 | public function getThumbnail() |
352 | { | 353 | { |
@@ -356,7 +357,7 @@ class Bookmark | |||
356 | /** | 357 | /** |
357 | * Set the Thumbnail. | 358 | * Set the Thumbnail. |
358 | * | 359 | * |
359 | * @param string|bool $thumbnail | 360 | * @param string|bool $thumbnail Thumbnail's URL - false if no thumbnail could be found |
360 | * | 361 | * |
361 | * @return Bookmark | 362 | * @return Bookmark |
362 | */ | 363 | */ |
@@ -405,7 +406,7 @@ class Bookmark | |||
405 | public function isNote() | 406 | public function isNote() |
406 | { | 407 | { |
407 | // We check empty value to get a valid result if the link has not been saved yet | 408 | // We check empty value to get a valid result if the link has not been saved yet |
408 | return empty($this->url) || $this->url[0] === '?'; | 409 | return empty($this->url) || startsWith($this->url, '/shaare/') || $this->url[0] === '?'; |
409 | } | 410 | } |
410 | 411 | ||
411 | /** | 412 | /** |
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php index 9c59e139..b3a90ed4 100644 --- a/application/bookmark/BookmarkFileService.php +++ b/application/bookmark/BookmarkFileService.php | |||
@@ -6,12 +6,14 @@ namespace Shaarli\Bookmark; | |||
6 | 6 | ||
7 | use Exception; | 7 | use Exception; |
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
9 | use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; | ||
9 | use Shaarli\Bookmark\Exception\EmptyDataStoreException; | 10 | use Shaarli\Bookmark\Exception\EmptyDataStoreException; |
10 | use Shaarli\Config\ConfigManager; | 11 | use Shaarli\Config\ConfigManager; |
11 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | 12 | use Shaarli\Formatter\BookmarkMarkdownFormatter; |
12 | use Shaarli\History; | 13 | use Shaarli\History; |
13 | use Shaarli\Legacy\LegacyLinkDB; | 14 | use Shaarli\Legacy\LegacyLinkDB; |
14 | use Shaarli\Legacy\LegacyUpdater; | 15 | use Shaarli\Legacy\LegacyUpdater; |
16 | use Shaarli\Render\PageCacheManager; | ||
15 | use Shaarli\Updater\UpdaterUtils; | 17 | use Shaarli\Updater\UpdaterUtils; |
16 | 18 | ||
17 | /** | 19 | /** |
@@ -39,6 +41,9 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
39 | /** @var History instance */ | 41 | /** @var History instance */ |
40 | protected $history; | 42 | protected $history; |
41 | 43 | ||
44 | /** @var PageCacheManager instance */ | ||
45 | protected $pageCacheManager; | ||
46 | |||
42 | /** @var bool true for logged in users. Default value to retrieve private bookmarks. */ | 47 | /** @var bool true for logged in users. Default value to retrieve private bookmarks. */ |
43 | protected $isLoggedIn; | 48 | protected $isLoggedIn; |
44 | 49 | ||
@@ -49,6 +54,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
49 | { | 54 | { |
50 | $this->conf = $conf; | 55 | $this->conf = $conf; |
51 | $this->history = $history; | 56 | $this->history = $history; |
57 | $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn); | ||
52 | $this->bookmarksIO = new BookmarkIO($this->conf); | 58 | $this->bookmarksIO = new BookmarkIO($this->conf); |
53 | $this->isLoggedIn = $isLoggedIn; | 59 | $this->isLoggedIn = $isLoggedIn; |
54 | 60 | ||
@@ -57,10 +63,16 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
57 | } else { | 63 | } else { |
58 | try { | 64 | try { |
59 | $this->bookmarks = $this->bookmarksIO->read(); | 65 | $this->bookmarks = $this->bookmarksIO->read(); |
60 | } catch (EmptyDataStoreException $e) { | 66 | } catch (EmptyDataStoreException|DatastoreNotInitializedException $e) { |
61 | $this->bookmarks = new BookmarkArray(); | 67 | $this->bookmarks = new BookmarkArray(); |
62 | if ($isLoggedIn) { | 68 | |
63 | $this->save(); | 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 | } | ||
64 | } | 76 | } |
65 | } | 77 | } |
66 | 78 | ||
@@ -88,7 +100,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
88 | throw new Exception('Not authorized'); | 100 | throw new Exception('Not authorized'); |
89 | } | 101 | } |
90 | 102 | ||
91 | return $bookmark; | 103 | return $first; |
92 | } | 104 | } |
93 | 105 | ||
94 | /** | 106 | /** |
@@ -149,7 +161,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
149 | */ | 161 | */ |
150 | public function set($bookmark, $save = true) | 162 | public function set($bookmark, $save = true) |
151 | { | 163 | { |
152 | if ($this->isLoggedIn !== true) { | 164 | if (true !== $this->isLoggedIn) { |
153 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 165 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
154 | } | 166 | } |
155 | if (! $bookmark instanceof Bookmark) { | 167 | if (! $bookmark instanceof Bookmark) { |
@@ -174,7 +186,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
174 | */ | 186 | */ |
175 | public function add($bookmark, $save = true) | 187 | public function add($bookmark, $save = true) |
176 | { | 188 | { |
177 | if ($this->isLoggedIn !== true) { | 189 | if (true !== $this->isLoggedIn) { |
178 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 190 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
179 | } | 191 | } |
180 | if (! $bookmark instanceof Bookmark) { | 192 | if (! $bookmark instanceof Bookmark) { |
@@ -199,7 +211,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
199 | */ | 211 | */ |
200 | public function addOrSet($bookmark, $save = true) | 212 | public function addOrSet($bookmark, $save = true) |
201 | { | 213 | { |
202 | if ($this->isLoggedIn !== true) { | 214 | if (true !== $this->isLoggedIn) { |
203 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 215 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
204 | } | 216 | } |
205 | if (! $bookmark instanceof Bookmark) { | 217 | if (! $bookmark instanceof Bookmark) { |
@@ -216,7 +228,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
216 | */ | 228 | */ |
217 | public function remove($bookmark, $save = true) | 229 | public function remove($bookmark, $save = true) |
218 | { | 230 | { |
219 | if ($this->isLoggedIn !== true) { | 231 | if (true !== $this->isLoggedIn) { |
220 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 232 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
221 | } | 233 | } |
222 | if (! $bookmark instanceof Bookmark) { | 234 | if (! $bookmark instanceof Bookmark) { |
@@ -269,13 +281,14 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
269 | */ | 281 | */ |
270 | public function save() | 282 | public function save() |
271 | { | 283 | { |
272 | if (!$this->isLoggedIn) { | 284 | if (true !== $this->isLoggedIn) { |
273 | // TODO: raise an Exception instead | 285 | // TODO: raise an Exception instead |
274 | die('You are not authorized to change the database.'); | 286 | die('You are not authorized to change the database.'); |
275 | } | 287 | } |
288 | |||
276 | $this->bookmarks->reorder(); | 289 | $this->bookmarks->reorder(); |
277 | $this->bookmarksIO->write($this->bookmarks); | 290 | $this->bookmarksIO->write($this->bookmarks); |
278 | invalidateCaches($this->conf->get('resource.page_cache')); | 291 | $this->pageCacheManager->invalidateCaches(); |
279 | } | 292 | } |
280 | 293 | ||
281 | /** | 294 | /** |
@@ -291,6 +304,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
291 | if (empty($tag) | 304 | if (empty($tag) |
292 | || (! $this->isLoggedIn && startsWith($tag, '.')) | 305 | || (! $this->isLoggedIn && startsWith($tag, '.')) |
293 | || $tag === BookmarkMarkdownFormatter::NO_MD_TAG | 306 | || $tag === BookmarkMarkdownFormatter::NO_MD_TAG |
307 | || in_array($tag, $filteringTags, true) | ||
294 | ) { | 308 | ) { |
295 | continue; | 309 | continue; |
296 | } | 310 | } |
@@ -349,6 +363,10 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
349 | { | 363 | { |
350 | $initializer = new BookmarkInitializer($this); | 364 | $initializer = new BookmarkInitializer($this); |
351 | $initializer->initialize(); | 365 | $initializer->initialize(); |
366 | |||
367 | if (true === $this->isLoggedIn) { | ||
368 | $this->save(); | ||
369 | } | ||
352 | } | 370 | } |
353 | 371 | ||
354 | /** | 372 | /** |
diff --git a/application/bookmark/BookmarkFilter.php b/application/bookmark/BookmarkFilter.php index fd556679..797a36b8 100644 --- a/application/bookmark/BookmarkFilter.php +++ b/application/bookmark/BookmarkFilter.php | |||
@@ -436,7 +436,7 @@ class BookmarkFilter | |||
436 | throw new Exception('Invalid date format'); | 436 | throw new Exception('Invalid date format'); |
437 | } | 437 | } |
438 | 438 | ||
439 | $filtered = array(); | 439 | $filtered = []; |
440 | foreach ($this->bookmarks as $key => $l) { | 440 | foreach ($this->bookmarks as $key => $l) { |
441 | if ($l->getCreated()->format('Ymd') == $day) { | 441 | if ($l->getCreated()->format('Ymd') == $day) { |
442 | $filtered[$key] = $l; | 442 | $filtered[$key] = $l; |
diff --git a/application/bookmark/BookmarkIO.php b/application/bookmark/BookmarkIO.php index ae9ffcb4..6bf7f365 100644 --- a/application/bookmark/BookmarkIO.php +++ b/application/bookmark/BookmarkIO.php | |||
@@ -2,6 +2,7 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Bookmark; |
4 | 4 | ||
5 | use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; | ||
5 | use Shaarli\Bookmark\Exception\EmptyDataStoreException; | 6 | use Shaarli\Bookmark\Exception\EmptyDataStoreException; |
6 | use Shaarli\Bookmark\Exception\NotWritableDataStoreException; | 7 | use Shaarli\Bookmark\Exception\NotWritableDataStoreException; |
7 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
@@ -52,13 +53,14 @@ class BookmarkIO | |||
52 | * | 53 | * |
53 | * @return BookmarkArray instance | 54 | * @return BookmarkArray instance |
54 | * | 55 | * |
55 | * @throws NotWritableDataStoreException Data couldn't be loaded | 56 | * @throws NotWritableDataStoreException Data couldn't be loaded |
56 | * @throws EmptyDataStoreException Datastore doesn't exist | 57 | * @throws EmptyDataStoreException Datastore file exists but does not contain any bookmark |
58 | * @throws DatastoreNotInitializedException File does not exists | ||
57 | */ | 59 | */ |
58 | public function read() | 60 | public function read() |
59 | { | 61 | { |
60 | if (! file_exists($this->datastore)) { | 62 | if (! file_exists($this->datastore)) { |
61 | throw new EmptyDataStoreException(); | 63 | throw new DatastoreNotInitializedException(); |
62 | } | 64 | } |
63 | 65 | ||
64 | if (!is_writable($this->datastore)) { | 66 | if (!is_writable($this->datastore)) { |
@@ -102,7 +104,5 @@ class BookmarkIO | |||
102 | $this->datastore, | 104 | $this->datastore, |
103 | self::$phpPrefix.base64_encode(gzdeflate(serialize($links))).self::$phpSuffix | 105 | self::$phpPrefix.base64_encode(gzdeflate(serialize($links))).self::$phpSuffix |
104 | ); | 106 | ); |
105 | |||
106 | invalidateCaches($this->conf->get('resource.page_cache')); | ||
107 | } | 107 | } |
108 | } | 108 | } |
diff --git a/application/bookmark/BookmarkInitializer.php b/application/bookmark/BookmarkInitializer.php index 9eee9a35..cd2d1724 100644 --- a/application/bookmark/BookmarkInitializer.php +++ b/application/bookmark/BookmarkInitializer.php | |||
@@ -6,8 +6,7 @@ namespace Shaarli\Bookmark; | |||
6 | * Class BookmarkInitializer | 6 | * Class BookmarkInitializer |
7 | * | 7 | * |
8 | * This class is used to initialized default bookmarks after a fresh install of Shaarli. | 8 | * This class is used to initialized default bookmarks after a fresh install of Shaarli. |
9 | * It is no longer call when the data store is empty, | 9 | * It should be only called if the datastore file does not exist(users might want to delete the default bookmarks). |
10 | * because user might want to delete default bookmarks after the install. | ||
11 | * | 10 | * |
12 | * To prevent data corruption, it does not overwrite existing bookmarks, | 11 | * To prevent data corruption, it does not overwrite existing bookmarks, |
13 | * even though there should not be any. | 12 | * even though there should not be any. |
@@ -36,11 +35,11 @@ class BookmarkInitializer | |||
36 | { | 35 | { |
37 | $bookmark = new Bookmark(); | 36 | $bookmark = new Bookmark(); |
38 | $bookmark->setTitle(t('My secret stuff... - Pastebin.com')); | 37 | $bookmark->setTitle(t('My secret stuff... - Pastebin.com')); |
39 | $bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', []); | 38 | $bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8='); |
40 | $bookmark->setDescription(t('Shhhh! I\'m a private link only YOU can see. You can delete me too.')); | 39 | $bookmark->setDescription(t('Shhhh! I\'m a private link only YOU can see. You can delete me too.')); |
41 | $bookmark->setTagsString('secretstuff'); | 40 | $bookmark->setTagsString('secretstuff'); |
42 | $bookmark->setPrivate(true); | 41 | $bookmark->setPrivate(true); |
43 | $this->bookmarkService->add($bookmark); | 42 | $this->bookmarkService->add($bookmark, false); |
44 | 43 | ||
45 | $bookmark = new Bookmark(); | 44 | $bookmark = new Bookmark(); |
46 | $bookmark->setTitle(t('The personal, minimalist, super-fast, database free, bookmarking service')); | 45 | $bookmark->setTitle(t('The personal, minimalist, super-fast, database free, bookmarking service')); |
@@ -54,6 +53,6 @@ To learn how to use Shaarli, consult the link "Documentation" at the bottom of t | |||
54 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.' | 53 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.' |
55 | )); | 54 | )); |
56 | $bookmark->setTagsString('opensource software'); | 55 | $bookmark->setTagsString('opensource software'); |
57 | $this->bookmarkService->add($bookmark); | 56 | $this->bookmarkService->add($bookmark, false); |
58 | } | 57 | } |
59 | } | 58 | } |
diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php index 7b7a4f09..ce8bd912 100644 --- a/application/bookmark/BookmarkServiceInterface.php +++ b/application/bookmark/BookmarkServiceInterface.php | |||
@@ -6,7 +6,6 @@ namespace Shaarli\Bookmark; | |||
6 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 6 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
7 | use Shaarli\Bookmark\Exception\NotWritableDataStoreException; | 7 | use Shaarli\Bookmark\Exception\NotWritableDataStoreException; |
8 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Exceptions\IOException; | ||
10 | use Shaarli\History; | 9 | use Shaarli\History; |
11 | 10 | ||
12 | /** | 11 | /** |
diff --git a/application/bookmark/LinkUtils.php b/application/bookmark/LinkUtils.php index 88379430..68914fca 100644 --- a/application/bookmark/LinkUtils.php +++ b/application/bookmark/LinkUtils.php | |||
@@ -3,112 +3,6 @@ | |||
3 | use Shaarli\Bookmark\Bookmark; | 3 | use Shaarli\Bookmark\Bookmark; |
4 | 4 | ||
5 | /** | 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 | |||
111 | /** | ||
112 | * Extract title from an HTML document. | 6 | * Extract title from an HTML document. |
113 | * | 7 | * |
114 | * @param string $html HTML content where to look for a title. | 8 | * @param string $html HTML content where to look for a title. |
@@ -220,7 +114,7 @@ function hashtag_autolink($description, $indexUrl = '') | |||
220 | * \p{Mn} - any non marking space (accents, umlauts, etc) | 114 | * \p{Mn} - any non marking space (accents, umlauts, etc) |
221 | */ | 115 | */ |
222 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; | 116 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; |
223 | $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>'; |
224 | return preg_replace($regex, $replacement, $description); | 118 | return preg_replace($regex, $replacement, $description); |
225 | } | 119 | } |
226 | 120 | ||
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/config/ConfigManager.php b/application/config/ConfigManager.php index e45bb4c3..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 | ||
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 index e2c78ccc..58067c99 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php | |||
@@ -7,11 +7,21 @@ namespace Shaarli\Container; | |||
7 | use Shaarli\Bookmark\BookmarkFileService; | 7 | use Shaarli\Bookmark\BookmarkFileService; |
8 | use Shaarli\Bookmark\BookmarkServiceInterface; | 8 | use Shaarli\Bookmark\BookmarkServiceInterface; |
9 | use Shaarli\Config\ConfigManager; | 9 | use Shaarli\Config\ConfigManager; |
10 | use Shaarli\Feed\FeedBuilder; | ||
11 | use Shaarli\Formatter\FormatterFactory; | ||
12 | use Shaarli\Front\Controller\Visitor\ErrorController; | ||
10 | use Shaarli\History; | 13 | use Shaarli\History; |
14 | use Shaarli\Http\HttpAccess; | ||
15 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
11 | use Shaarli\Plugin\PluginManager; | 16 | use Shaarli\Plugin\PluginManager; |
12 | use Shaarli\Render\PageBuilder; | 17 | use Shaarli\Render\PageBuilder; |
18 | use Shaarli\Render\PageCacheManager; | ||
19 | use Shaarli\Security\CookieManager; | ||
13 | use Shaarli\Security\LoginManager; | 20 | use Shaarli\Security\LoginManager; |
14 | use Shaarli\Security\SessionManager; | 21 | use Shaarli\Security\SessionManager; |
22 | use Shaarli\Thumbnailer; | ||
23 | use Shaarli\Updater\Updater; | ||
24 | use Shaarli\Updater\UpdaterUtils; | ||
15 | 25 | ||
16 | /** | 26 | /** |
17 | * Class ContainerBuilder | 27 | * Class ContainerBuilder |
@@ -30,22 +40,37 @@ class ContainerBuilder | |||
30 | /** @var SessionManager */ | 40 | /** @var SessionManager */ |
31 | protected $session; | 41 | protected $session; |
32 | 42 | ||
43 | /** @var CookieManager */ | ||
44 | protected $cookieManager; | ||
45 | |||
33 | /** @var LoginManager */ | 46 | /** @var LoginManager */ |
34 | protected $login; | 47 | protected $login; |
35 | 48 | ||
36 | public function __construct(ConfigManager $conf, SessionManager $session, LoginManager $login) | 49 | /** @var string|null */ |
37 | { | 50 | protected $basePath = null; |
51 | |||
52 | public function __construct( | ||
53 | ConfigManager $conf, | ||
54 | SessionManager $session, | ||
55 | CookieManager $cookieManager, | ||
56 | LoginManager $login | ||
57 | ) { | ||
38 | $this->conf = $conf; | 58 | $this->conf = $conf; |
39 | $this->session = $session; | 59 | $this->session = $session; |
40 | $this->login = $login; | 60 | $this->login = $login; |
61 | $this->cookieManager = $cookieManager; | ||
41 | } | 62 | } |
42 | 63 | ||
43 | public function build(): ShaarliContainer | 64 | public function build(): ShaarliContainer |
44 | { | 65 | { |
45 | $container = new ShaarliContainer(); | 66 | $container = new ShaarliContainer(); |
67 | |||
46 | $container['conf'] = $this->conf; | 68 | $container['conf'] = $this->conf; |
47 | $container['sessionManager'] = $this->session; | 69 | $container['sessionManager'] = $this->session; |
70 | $container['cookieManager'] = $this->cookieManager; | ||
48 | $container['loginManager'] = $this->login; | 71 | $container['loginManager'] = $this->login; |
72 | $container['basePath'] = $this->basePath; | ||
73 | |||
49 | $container['plugins'] = function (ShaarliContainer $container): PluginManager { | 74 | $container['plugins'] = function (ShaarliContainer $container): PluginManager { |
50 | return new PluginManager($container->conf); | 75 | return new PluginManager($container->conf); |
51 | }; | 76 | }; |
@@ -73,7 +98,62 @@ class ContainerBuilder | |||
73 | }; | 98 | }; |
74 | 99 | ||
75 | $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { | 100 | $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { |
76 | return new PluginManager($container->conf); | 101 | $pluginManager = new PluginManager($container->conf); |
102 | |||
103 | $pluginManager->load($container->conf->get('general.enabled_plugins')); | ||
104 | |||
105 | return $pluginManager; | ||
106 | }; | ||
107 | |||
108 | $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { | ||
109 | return new FormatterFactory( | ||
110 | $container->conf, | ||
111 | $container->loginManager->isLoggedIn() | ||
112 | ); | ||
113 | }; | ||
114 | |||
115 | $container['pageCacheManager'] = function (ShaarliContainer $container): PageCacheManager { | ||
116 | return new PageCacheManager( | ||
117 | $container->conf->get('resource.page_cache'), | ||
118 | $container->loginManager->isLoggedIn() | ||
119 | ); | ||
120 | }; | ||
121 | |||
122 | $container['feedBuilder'] = function (ShaarliContainer $container): FeedBuilder { | ||
123 | return new FeedBuilder( | ||
124 | $container->bookmarkService, | ||
125 | $container->formatterFactory->getFormatter(), | ||
126 | $container->environment, | ||
127 | $container->loginManager->isLoggedIn() | ||
128 | ); | ||
129 | }; | ||
130 | |||
131 | $container['thumbnailer'] = function (ShaarliContainer $container): Thumbnailer { | ||
132 | return new Thumbnailer($container->conf); | ||
133 | }; | ||
134 | |||
135 | $container['httpAccess'] = function (): HttpAccess { | ||
136 | return new HttpAccess(); | ||
137 | }; | ||
138 | |||
139 | $container['netscapeBookmarkUtils'] = function (ShaarliContainer $container): NetscapeBookmarkUtils { | ||
140 | return new NetscapeBookmarkUtils($container->bookmarkService, $container->conf, $container->history); | ||
141 | }; | ||
142 | |||
143 | $container['updater'] = function (ShaarliContainer $container): Updater { | ||
144 | return new Updater( | ||
145 | UpdaterUtils::read_updates_file($container->conf->get('resource.updates')), | ||
146 | $container->bookmarkService, | ||
147 | $container->conf, | ||
148 | $container->loginManager->isLoggedIn() | ||
149 | ); | ||
150 | }; | ||
151 | |||
152 | $container['errorHandler'] = function (ShaarliContainer $container): ErrorController { | ||
153 | return new ErrorController($container); | ||
154 | }; | ||
155 | $container['phpErrorHandler'] = function (ShaarliContainer $container): ErrorController { | ||
156 | return new ErrorController($container); | ||
77 | }; | 157 | }; |
78 | 158 | ||
79 | return $container; | 159 | return $container; |
diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php index 3fa9116e..9a9a974a 100644 --- a/application/container/ShaarliContainer.php +++ b/application/container/ShaarliContainer.php | |||
@@ -6,23 +6,43 @@ namespace Shaarli\Container; | |||
6 | 6 | ||
7 | use Shaarli\Bookmark\BookmarkServiceInterface; | 7 | use Shaarli\Bookmark\BookmarkServiceInterface; |
8 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Feed\FeedBuilder; | ||
10 | use Shaarli\Formatter\FormatterFactory; | ||
9 | use Shaarli\History; | 11 | use Shaarli\History; |
12 | use Shaarli\Http\HttpAccess; | ||
13 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
10 | use Shaarli\Plugin\PluginManager; | 14 | use Shaarli\Plugin\PluginManager; |
11 | use Shaarli\Render\PageBuilder; | 15 | use Shaarli\Render\PageBuilder; |
16 | use Shaarli\Render\PageCacheManager; | ||
17 | use Shaarli\Security\CookieManager; | ||
12 | use Shaarli\Security\LoginManager; | 18 | use Shaarli\Security\LoginManager; |
13 | use Shaarli\Security\SessionManager; | 19 | use Shaarli\Security\SessionManager; |
20 | use Shaarli\Thumbnailer; | ||
21 | use Shaarli\Updater\Updater; | ||
14 | use Slim\Container; | 22 | use Slim\Container; |
15 | 23 | ||
16 | /** | 24 | /** |
17 | * Extension of Slim container to document the injected objects. | 25 | * Extension of Slim container to document the injected objects. |
18 | * | 26 | * |
27 | * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`) | ||
28 | * @property BookmarkServiceInterface $bookmarkService | ||
29 | * @property CookieManager $cookieManager | ||
19 | * @property ConfigManager $conf | 30 | * @property ConfigManager $conf |
20 | * @property SessionManager $sessionManager | 31 | * @property mixed[] $environment $_SERVER automatically injected by Slim |
21 | * @property LoginManager $loginManager | 32 | * @property callable $errorHandler Overrides default Slim exception display |
33 | * @property FeedBuilder $feedBuilder | ||
34 | * @property FormatterFactory $formatterFactory | ||
22 | * @property History $history | 35 | * @property History $history |
23 | * @property BookmarkServiceInterface $bookmarkService | 36 | * @property HttpAccess $httpAccess |
37 | * @property LoginManager $loginManager | ||
38 | * @property NetscapeBookmarkUtils $netscapeBookmarkUtils | ||
24 | * @property PageBuilder $pageBuilder | 39 | * @property PageBuilder $pageBuilder |
40 | * @property PageCacheManager $pageCacheManager | ||
41 | * @property callable $phpErrorHandler Overrides default Slim PHP error display | ||
25 | * @property PluginManager $pluginManager | 42 | * @property PluginManager $pluginManager |
43 | * @property SessionManager $sessionManager | ||
44 | * @property Thumbnailer $thumbnailer | ||
45 | * @property Updater $updater | ||
26 | */ | 46 | */ |
27 | class ShaarliContainer extends Container | 47 | class ShaarliContainer extends Container |
28 | { | 48 | { |
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 40bd4f15..269ad877 100644 --- a/application/feed/FeedBuilder.php +++ b/application/feed/FeedBuilder.php | |||
@@ -43,22 +43,10 @@ class FeedBuilder | |||
43 | */ | 43 | */ |
44 | protected $formatter; | 44 | protected $formatter; |
45 | 45 | ||
46 | /** | 46 | /** @var mixed[] $_SERVER */ |
47 | * @var string RSS or ATOM feed. | ||
48 | */ | ||
49 | protected $feedType; | ||
50 | |||
51 | /** | ||
52 | * @var array $_SERVER | ||
53 | */ | ||
54 | protected $serverInfo; | 47 | protected $serverInfo; |
55 | 48 | ||
56 | /** | 49 | /** |
57 | * @var array $_GET | ||
58 | */ | ||
59 | protected $userInput; | ||
60 | |||
61 | /** | ||
62 | * @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. |
63 | */ | 51 | */ |
64 | protected $isLoggedIn; | 52 | protected $isLoggedIn; |
@@ -77,7 +65,6 @@ class FeedBuilder | |||
77 | * @var string server locale. | 65 | * @var string server locale. |
78 | */ | 66 | */ |
79 | protected $locale; | 67 | protected $locale; |
80 | |||
81 | /** | 68 | /** |
82 | * @var DateTime Latest item date. | 69 | * @var DateTime Latest item date. |
83 | */ | 70 | */ |
@@ -88,37 +75,36 @@ class FeedBuilder | |||
88 | * | 75 | * |
89 | * @param BookmarkServiceInterface $linkDB LinkDB instance. | 76 | * @param BookmarkServiceInterface $linkDB LinkDB instance. |
90 | * @param BookmarkFormatter $formatter instance. | 77 | * @param BookmarkFormatter $formatter instance. |
91 | * @param string $feedType Type of feed. | ||
92 | * @param array $serverInfo $_SERVER. | 78 | * @param array $serverInfo $_SERVER. |
93 | * @param array $userInput $_GET. | ||
94 | * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. | 79 | * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. |
95 | */ | 80 | */ |
96 | public function __construct($linkDB, $formatter, $feedType, $serverInfo, $userInput, $isLoggedIn) | 81 | public function __construct($linkDB, $formatter, $serverInfo, $isLoggedIn) |
97 | { | 82 | { |
98 | $this->linkDB = $linkDB; | 83 | $this->linkDB = $linkDB; |
99 | $this->formatter = $formatter; | 84 | $this->formatter = $formatter; |
100 | $this->feedType = $feedType; | ||
101 | $this->serverInfo = $serverInfo; | 85 | $this->serverInfo = $serverInfo; |
102 | $this->userInput = $userInput; | ||
103 | $this->isLoggedIn = $isLoggedIn; | 86 | $this->isLoggedIn = $isLoggedIn; |
104 | } | 87 | } |
105 | 88 | ||
106 | /** | 89 | /** |
107 | * Build data for feed templates. | 90 | * Build data for feed templates. |
108 | * | 91 | * |
92 | * @param string $feedType Type of feed (RSS/ATOM). | ||
93 | * @param array $userInput $_GET. | ||
94 | * | ||
109 | * @return array Formatted data for feeds templates. | 95 | * @return array Formatted data for feeds templates. |
110 | */ | 96 | */ |
111 | public function buildData() | 97 | public function buildData(string $feedType, ?array $userInput) |
112 | { | 98 | { |
113 | // Search for untagged bookmarks | 99 | // Search for untagged bookmarks |
114 | if (isset($this->userInput['searchtags']) && empty($this->userInput['searchtags'])) { | 100 | if (isset($this->userInput['searchtags']) && empty($userInput['searchtags'])) { |
115 | $this->userInput['searchtags'] = false; | 101 | $userInput['searchtags'] = false; |
116 | } | 102 | } |
117 | 103 | ||
118 | // Optionally filter the results: | 104 | // Optionally filter the results: |
119 | $linksToDisplay = $this->linkDB->search($this->userInput); | 105 | $linksToDisplay = $this->linkDB->search($userInput); |
120 | 106 | ||
121 | $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay)); | 107 | $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay), $userInput); |
122 | 108 | ||
123 | // 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. |
124 | $keys = array(); | 110 | $keys = array(); |
@@ -130,11 +116,11 @@ class FeedBuilder | |||
130 | $this->formatter->addContextData('index_url', $pageaddr); | 116 | $this->formatter->addContextData('index_url', $pageaddr); |
131 | $linkDisplayed = array(); | 117 | $linkDisplayed = array(); |
132 | for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) { | 118 | for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) { |
133 | $linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr); | 119 | $linkDisplayed[$keys[$i]] = $this->buildItem($feedType, $linksToDisplay[$keys[$i]], $pageaddr); |
134 | } | 120 | } |
135 | 121 | ||
136 | $data['language'] = $this->getTypeLanguage(); | 122 | $data['language'] = $this->getTypeLanguage($feedType); |
137 | $data['last_update'] = $this->getLatestDateFormatted(); | 123 | $data['last_update'] = $this->getLatestDateFormatted($feedType); |
138 | $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; | 124 | $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; |
139 | // Remove leading slash from REQUEST_URI. | 125 | // Remove leading slash from REQUEST_URI. |
140 | $data['self_link'] = escape(server_url($this->serverInfo)) | 126 | $data['self_link'] = escape(server_url($this->serverInfo)) |
@@ -147,17 +133,48 @@ class FeedBuilder | |||
147 | } | 133 | } |
148 | 134 | ||
149 | /** | 135 | /** |
136 | * Set this to true to use permalinks instead of direct bookmarks. | ||
137 | * | ||
138 | * @param boolean $usePermalinks true to force permalinks. | ||
139 | */ | ||
140 | public function setUsePermalinks($usePermalinks) | ||
141 | { | ||
142 | $this->usePermalinks = $usePermalinks; | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * Set this to true to hide timestamps in feeds. | ||
147 | * | ||
148 | * @param boolean $hideDates true to enable. | ||
149 | */ | ||
150 | public function setHideDates($hideDates) | ||
151 | { | ||
152 | $this->hideDates = $hideDates; | ||
153 | } | ||
154 | |||
155 | /** | ||
156 | * Set the locale. Used to show feed language. | ||
157 | * | ||
158 | * @param string $locale The locale (eg. 'fr_FR.UTF8'). | ||
159 | */ | ||
160 | public function setLocale($locale) | ||
161 | { | ||
162 | $this->locale = strtolower($locale); | ||
163 | } | ||
164 | |||
165 | /** | ||
150 | * Build a feed item (one per shaare). | 166 | * Build a feed item (one per shaare). |
151 | * | 167 | * |
168 | * @param string $feedType Type of feed (RSS/ATOM). | ||
152 | * @param Bookmark $link Single link array extracted from LinkDB. | 169 | * @param Bookmark $link Single link array extracted from LinkDB. |
153 | * @param string $pageaddr Index URL. | 170 | * @param string $pageaddr Index URL. |
154 | * | 171 | * |
155 | * @return array Link array with feed attributes. | 172 | * @return array Link array with feed attributes. |
156 | */ | 173 | */ |
157 | protected function buildItem($link, $pageaddr) | 174 | protected function buildItem(string $feedType, $link, $pageaddr) |
158 | { | 175 | { |
159 | $data = $this->formatter->format($link); | 176 | $data = $this->formatter->format($link); |
160 | $data['guid'] = $pageaddr . '?' . $data['shorturl']; | 177 | $data['guid'] = rtrim($pageaddr, '/') . '/shaare/' . $data['shorturl']; |
161 | if ($this->usePermalinks === true) { | 178 | if ($this->usePermalinks === true) { |
162 | $permalink = '<a href="'. $data['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>'; | 179 | $permalink = '<a href="'. $data['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>'; |
163 | } else { | 180 | } else { |
@@ -165,13 +182,13 @@ class FeedBuilder | |||
165 | } | 182 | } |
166 | $data['description'] .= PHP_EOL . PHP_EOL . '<br>— ' . $permalink; | 183 | $data['description'] .= PHP_EOL . PHP_EOL . '<br>— ' . $permalink; |
167 | 184 | ||
168 | $data['pub_iso_date'] = $this->getIsoDate($data['created']); | 185 | $data['pub_iso_date'] = $this->getIsoDate($feedType, $data['created']); |
169 | 186 | ||
170 | // atom:entry elements MUST contain exactly one atom:updated element. | 187 | // atom:entry elements MUST contain exactly one atom:updated element. |
171 | if (!empty($link->getUpdated())) { | 188 | if (!empty($link->getUpdated())) { |
172 | $data['up_iso_date'] = $this->getIsoDate($data['updated'], DateTime::ATOM); | 189 | $data['up_iso_date'] = $this->getIsoDate($feedType, $data['updated'], DateTime::ATOM); |
173 | } else { | 190 | } else { |
174 | $data['up_iso_date'] = $this->getIsoDate($data['created'], DateTime::ATOM); | 191 | $data['up_iso_date'] = $this->getIsoDate($feedType, $data['created'], DateTime::ATOM); |
175 | } | 192 | } |
176 | 193 | ||
177 | // Save the more recent item. | 194 | // Save the more recent item. |
@@ -186,51 +203,23 @@ class FeedBuilder | |||
186 | } | 203 | } |
187 | 204 | ||
188 | /** | 205 | /** |
189 | * Set this to true to use permalinks instead of direct bookmarks. | ||
190 | * | ||
191 | * @param boolean $usePermalinks true to force permalinks. | ||
192 | */ | ||
193 | public function setUsePermalinks($usePermalinks) | ||
194 | { | ||
195 | $this->usePermalinks = $usePermalinks; | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * Set this to true to hide timestamps in feeds. | ||
200 | * | ||
201 | * @param boolean $hideDates true to enable. | ||
202 | */ | ||
203 | public function setHideDates($hideDates) | ||
204 | { | ||
205 | $this->hideDates = $hideDates; | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * Set the locale. Used to show feed language. | ||
210 | * | ||
211 | * @param string $locale The locale (eg. 'fr_FR.UTF8'). | ||
212 | */ | ||
213 | public function setLocale($locale) | ||
214 | { | ||
215 | $this->locale = strtolower($locale); | ||
216 | } | ||
217 | |||
218 | /** | ||
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); |
@@ -275,21 +267,22 @@ class FeedBuilder | |||
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 bookmarks (max parameter). | 268 | * If 'nb' is set to 'all', display all filtered bookmarks (max parameter). |
277 | * | 269 | * |
278 | * @param int $max maximum number of bookmarks to display. | 270 | * @param int $max maximum number of bookmarks to display. |
271 | * @param array $userInput $_GET. | ||
279 | * | 272 | * |
280 | * @return int number of bookmarks 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 index c6c59064..9d4a0fa0 100644 --- a/application/formatter/BookmarkDefaultFormatter.php +++ b/application/formatter/BookmarkDefaultFormatter.php | |||
@@ -50,11 +50,10 @@ class BookmarkDefaultFormatter extends BookmarkFormatter | |||
50 | */ | 50 | */ |
51 | public function formatUrl($bookmark) | 51 | public function formatUrl($bookmark) |
52 | { | 52 | { |
53 | if (! empty($this->contextData['index_url']) && ( | 53 | if ($bookmark->isNote() && isset($this->contextData['index_url'])) { |
54 | startsWith($bookmark->getUrl(), '?') || startsWith($bookmark->getUrl(), '/') | 54 | return rtrim($this->contextData['index_url'], '/') . '/' . escape(ltrim($bookmark->getUrl(), '/')); |
55 | )) { | ||
56 | return $this->contextData['index_url'] . escape($bookmark->getUrl()); | ||
57 | } | 55 | } |
56 | |||
58 | return escape($bookmark->getUrl()); | 57 | return escape($bookmark->getUrl()); |
59 | } | 58 | } |
60 | 59 | ||
@@ -63,11 +62,18 @@ class BookmarkDefaultFormatter extends BookmarkFormatter | |||
63 | */ | 62 | */ |
64 | protected function formatRealUrl($bookmark) | 63 | protected function formatRealUrl($bookmark) |
65 | { | 64 | { |
66 | if (! empty($this->contextData['index_url']) && ( | 65 | if ($bookmark->isNote()) { |
67 | startsWith($bookmark->getUrl(), '?') || startsWith($bookmark->getUrl(), '/') | 66 | if (isset($this->contextData['index_url'])) { |
68 | )) { | 67 | $prefix = rtrim($this->contextData['index_url'], '/') . '/'; |
69 | return $this->contextData['index_url'] . escape($bookmark->getUrl()); | 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(), '/')); | ||
70 | } | 75 | } |
76 | |||
71 | return escape($bookmark->getUrl()); | 77 | return escape($bookmark->getUrl()); |
72 | } | 78 | } |
73 | 79 | ||
diff --git a/application/formatter/BookmarkFormatter.php b/application/formatter/BookmarkFormatter.php index a80d83fc..22ba7aae 100644 --- a/application/formatter/BookmarkFormatter.php +++ b/application/formatter/BookmarkFormatter.php | |||
@@ -3,8 +3,8 @@ | |||
3 | namespace Shaarli\Formatter; | 3 | namespace Shaarli\Formatter; |
4 | 4 | ||
5 | use DateTime; | 5 | use DateTime; |
6 | use Shaarli\Config\ConfigManager; | ||
7 | use Shaarli\Bookmark\Bookmark; | 6 | use Shaarli\Bookmark\Bookmark; |
7 | use Shaarli\Config\ConfigManager; | ||
8 | 8 | ||
9 | /** | 9 | /** |
10 | * Class BookmarkFormatter | 10 | * Class BookmarkFormatter |
@@ -80,6 +80,8 @@ abstract class BookmarkFormatter | |||
80 | public function addContextData($key, $value) | 80 | public function addContextData($key, $value) |
81 | { | 81 | { |
82 | $this->contextData[$key] = $value; | 82 | $this->contextData[$key] = $value; |
83 | |||
84 | return $this; | ||
83 | } | 85 | } |
84 | 86 | ||
85 | /** | 87 | /** |
@@ -128,7 +130,7 @@ abstract class BookmarkFormatter | |||
128 | */ | 130 | */ |
129 | protected function formatRealUrl($bookmark) | 131 | protected function formatRealUrl($bookmark) |
130 | { | 132 | { |
131 | return $bookmark->getUrl(); | 133 | return $this->formatUrl($bookmark); |
132 | } | 134 | } |
133 | 135 | ||
134 | /** | 136 | /** |
diff --git a/application/formatter/BookmarkMarkdownFormatter.php b/application/formatter/BookmarkMarkdownFormatter.php index 077e5312..5d244d4c 100644 --- a/application/formatter/BookmarkMarkdownFormatter.php +++ b/application/formatter/BookmarkMarkdownFormatter.php | |||
@@ -114,7 +114,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter | |||
114 | 114 | ||
115 | /** | 115 | /** |
116 | * Replace hashtag in Markdown links format | 116 | * Replace hashtag in Markdown links format |
117 | * E.g. `#hashtag` becomes `[#hashtag](?addtag=hashtag)` | 117 | * E.g. `#hashtag` becomes `[#hashtag](./add-tag/hashtag)` |
118 | * It includes the index URL if specified. | 118 | * It includes the index URL if specified. |
119 | * | 119 | * |
120 | * @param string $description | 120 | * @param string $description |
@@ -133,7 +133,7 @@ class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter | |||
133 | * \p{Mn} - any non marking space (accents, umlauts, etc) | 133 | * \p{Mn} - any non marking space (accents, umlauts, etc) |
134 | */ | 134 | */ |
135 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; | 135 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; |
136 | $replacement = '$1[#$2]('. $indexUrl .'?addtag=$2)'; | 136 | $replacement = '$1[#$2]('. $indexUrl .'./add-tag/$2)'; |
137 | 137 | ||
138 | $descriptionLines = explode(PHP_EOL, $description); | 138 | $descriptionLines = explode(PHP_EOL, $description); |
139 | $descriptionOut = ''; | 139 | $descriptionOut = ''; |
diff --git a/application/formatter/FormatterFactory.php b/application/formatter/FormatterFactory.php index 5f282f68..a029579f 100644 --- a/application/formatter/FormatterFactory.php +++ b/application/formatter/FormatterFactory.php | |||
@@ -38,7 +38,7 @@ class FormatterFactory | |||
38 | * | 38 | * |
39 | * @return BookmarkFormatter instance. | 39 | * @return BookmarkFormatter instance. |
40 | */ | 40 | */ |
41 | public function getFormatter(string $type = null) | 41 | public function getFormatter(string $type = null): BookmarkFormatter |
42 | { | 42 | { |
43 | $type = $type ? $type : $this->conf->get('formatter', 'default'); | 43 | $type = $type ? $type : $this->conf->get('formatter', 'default'); |
44 | $className = '\\Shaarli\\Formatter\\Bookmark'. ucfirst($type) .'Formatter'; | 44 | $className = '\\Shaarli\\Formatter\\Bookmark'. ucfirst($type) .'Formatter'; |
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 index fa6c6467..c015c0c6 100644 --- a/application/front/ShaarliMiddleware.php +++ b/application/front/ShaarliMiddleware.php | |||
@@ -3,7 +3,7 @@ | |||
3 | namespace Shaarli\Front; | 3 | namespace Shaarli\Front; |
4 | 4 | ||
5 | use Shaarli\Container\ShaarliContainer; | 5 | use Shaarli\Container\ShaarliContainer; |
6 | use Shaarli\Front\Exception\ShaarliException; | 6 | use Shaarli\Front\Exception\UnauthorizedException; |
7 | use Slim\Http\Request; | 7 | use Slim\Http\Request; |
8 | use Slim\Http\Response; | 8 | use Slim\Http\Response; |
9 | 9 | ||
@@ -24,6 +24,8 @@ class ShaarliMiddleware | |||
24 | 24 | ||
25 | /** | 25 | /** |
26 | * Middleware execution: | 26 | * Middleware execution: |
27 | * - run updates | ||
28 | * - if not logged in open shaarli, redirect to login | ||
27 | * - execute the controller | 29 | * - execute the controller |
28 | * - return the response | 30 | * - return the response |
29 | * | 31 | * |
@@ -35,23 +37,78 @@ class ShaarliMiddleware | |||
35 | * | 37 | * |
36 | * @return Response response. | 38 | * @return Response response. |
37 | */ | 39 | */ |
38 | public function __invoke(Request $request, Response $response, callable $next) | 40 | public function __invoke(Request $request, Response $response, callable $next): Response |
39 | { | 41 | { |
42 | $this->initBasePath($request); | ||
43 | |||
40 | try { | 44 | try { |
41 | $response = $next($request, $response); | 45 | if (!is_file($this->container->conf->getConfigFileExt()) |
42 | } catch (ShaarliException $e) { | 46 | && !in_array($next->getName(), ['displayInstall', 'saveInstall'], true) |
43 | $this->container->pageBuilder->assign('message', $e->getMessage()); | 47 | ) { |
44 | if ($this->container->conf->get('dev.debug', false)) { | 48 | return $response->withRedirect($this->container->basePath . '/install'); |
45 | $this->container->pageBuilder->assign( | ||
46 | 'stacktrace', | ||
47 | nl2br(get_class($this) .': '. $e->getTraceAsString()) | ||
48 | ); | ||
49 | } | 49 | } |
50 | 50 | ||
51 | $response = $response->withStatus($e->getCode()); | 51 | $this->runUpdates(); |
52 | $response = $response->write($this->container->pageBuilder->render('error')); | 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', 'atom', 'rss'], true) | ||
98 | ) { | ||
99 | throw new UnauthorizedException(); | ||
53 | } | 100 | } |
54 | 101 | ||
55 | return $response; | 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 | } | ||
56 | } | 113 | } |
57 | } | 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..33e1188e --- /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') { | ||
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 = escape([ | ||
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') ? 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 | ['add-shaare', '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 = [ | ||
349 | 'link' => $link, | ||
350 | 'link_is_new' => $isNew, | ||
351 | 'http_referer' => escape($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..0380ef1f --- /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 = escape(trim($request->getParam('fromtag') ?? '')); | ||
45 | $toTag = escape(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..0e09116e --- /dev/null +++ b/application/front/controller/admin/PluginsController.php | |||
@@ -0,0 +1,84 @@ | |||
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 | foreach ($parameters as $param => $value) { | ||
66 | $this->container->conf->set('plugins.'. $param, escape($value)); | ||
67 | } | ||
68 | } else { | ||
69 | $this->container->conf->set('general.enabled_plugins', save_plugin_config($parameters)); | ||
70 | } | ||
71 | |||
72 | $this->container->conf->write($this->container->loginManager->isLoggedIn()); | ||
73 | $this->container->history->updateSettings(); | ||
74 | |||
75 | $this->saveSuccessMessage(t('Setting successfully saved.')); | ||
76 | } catch (Exception $e) { | ||
77 | $this->saveErrorMessage( | ||
78 | t('Error while saving plugin configuration: ') . PHP_EOL . $e->getMessage() | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | return $this->redirect($response, '/admin/plugins'); | ||
83 | } | ||
84 | } | ||
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..3b5939bb --- /dev/null +++ b/application/front/controller/admin/ShaarliAdminController.php | |||
@@ -0,0 +1,73 @@ | |||
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\Controller\Visitor\ShaarliVisitorController; | ||
9 | use Shaarli\Front\Exception\UnauthorizedException; | ||
10 | use Shaarli\Front\Exception\WrongTokenException; | ||
11 | use Shaarli\Security\SessionManager; | ||
12 | use Slim\Http\Request; | ||
13 | |||
14 | /** | ||
15 | * Class ShaarliAdminController | ||
16 | * | ||
17 | * All admin controllers (for logged in users) MUST extend this abstract class. | ||
18 | * It makes sure that the user is properly logged in, and otherwise throw an exception | ||
19 | * which will redirect to the login page. | ||
20 | * | ||
21 | * @package Shaarli\Front\Controller\Admin | ||
22 | */ | ||
23 | abstract class ShaarliAdminController extends ShaarliVisitorController | ||
24 | { | ||
25 | /** | ||
26 | * Any persistent action to the config or data store must check the XSRF token validity. | ||
27 | */ | ||
28 | protected function checkToken(Request $request): bool | ||
29 | { | ||
30 | if (!$this->container->sessionManager->checkToken($request->getParam('token'))) { | ||
31 | throw new WrongTokenException(); | ||
32 | } | ||
33 | |||
34 | return true; | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * Save a SUCCESS message in user session, which will be displayed on any template page. | ||
39 | */ | ||
40 | protected function saveSuccessMessage(string $message): void | ||
41 | { | ||
42 | $this->saveMessage(SessionManager::KEY_SUCCESS_MESSAGES, $message); | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Save a WARNING message in user session, which will be displayed on any template page. | ||
47 | */ | ||
48 | protected function saveWarningMessage(string $message): void | ||
49 | { | ||
50 | $this->saveMessage(SessionManager::KEY_WARNING_MESSAGES, $message); | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * Save an ERROR message in user session, which will be displayed on any template page. | ||
55 | */ | ||
56 | protected function saveErrorMessage(string $message): void | ||
57 | { | ||
58 | $this->saveMessage(SessionManager::KEY_ERROR_MESSAGES, $message); | ||
59 | } | ||
60 | |||
61 | /** | ||
62 | * Use the sessionManager to save the provided message using the proper type. | ||
63 | * | ||
64 | * @param string $type successed/warnings/errors | ||
65 | */ | ||
66 | protected function saveMessage(string $type, string $message): void | ||
67 | { | ||
68 | $messages = $this->container->sessionManager->getSessionParameter($type) ?? []; | ||
69 | $messages[] = $message; | ||
70 | |||
71 | $this->container->sessionManager->setSessionParameter($type, $messages); | ||
72 | } | ||
73 | } | ||
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..2988bee6 --- /dev/null +++ b/application/front/controller/visitor/BookmarkListController.php | |||
@@ -0,0 +1,240 @@ | |||
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 = escape(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' => $searchTerm, | ||
108 | 'search_tags' => $searchTags, | ||
109 | 'visibility' => $visibility, | ||
110 | 'links' => $linkDisp, | ||
111 | ] | ||
112 | ); | ||
113 | |||
114 | if (!empty($searchTerm) || !empty($searchTags)) { | ||
115 | $data['pagetitle'] = t('Search: '); | ||
116 | $data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : ''; | ||
117 | $bracketWrap = function ($tag) { | ||
118 | return '[' . $tag . ']'; | ||
119 | }; | ||
120 | $data['pagetitle'] .= ! empty($searchTags) | ||
121 | ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchTags))) . ' ' | ||
122 | : ''; | ||
123 | $data['pagetitle'] .= '- '; | ||
124 | } | ||
125 | |||
126 | $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli'); | ||
127 | |||
128 | $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST); | ||
129 | $this->assignAllView($data); | ||
130 | |||
131 | return $response->write($this->render(TemplatePage::LINKLIST)); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * GET /shaare/{hash} - Display a single shaare | ||
136 | */ | ||
137 | public function permalink(Request $request, Response $response, array $args): Response | ||
138 | { | ||
139 | try { | ||
140 | $bookmark = $this->container->bookmarkService->findByHash($args['hash']); | ||
141 | } catch (BookmarkNotFoundException $e) { | ||
142 | $this->assignView('error_message', $e->getMessage()); | ||
143 | |||
144 | return $response->write($this->render(TemplatePage::ERROR_404)); | ||
145 | } | ||
146 | |||
147 | $this->updateThumbnail($bookmark); | ||
148 | |||
149 | $formatter = $this->container->formatterFactory->getFormatter(); | ||
150 | $formatter->addContextData('base_path', $this->container->basePath); | ||
151 | |||
152 | $data = array_merge( | ||
153 | $this->initializeTemplateVars(), | ||
154 | [ | ||
155 | 'pagetitle' => $bookmark->getTitle() .' - '. $this->container->conf->get('general.title', 'Shaarli'), | ||
156 | 'links' => [$formatter->format($bookmark)], | ||
157 | ] | ||
158 | ); | ||
159 | |||
160 | $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST); | ||
161 | $this->assignAllView($data); | ||
162 | |||
163 | return $response->write($this->render(TemplatePage::LINKLIST)); | ||
164 | } | ||
165 | |||
166 | /** | ||
167 | * Update the thumbnail of a single bookmark if necessary. | ||
168 | */ | ||
169 | protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool | ||
170 | { | ||
171 | // Logged in, thumbnails enabled, not a note, is HTTP | ||
172 | // and (never retrieved yet or no valid cache file) | ||
173 | if ($this->container->loginManager->isLoggedIn() | ||
174 | && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
175 | && false !== $bookmark->getThumbnail() | ||
176 | && !$bookmark->isNote() | ||
177 | && (null === $bookmark->getThumbnail() || !is_file($bookmark->getThumbnail())) | ||
178 | && startsWith(strtolower($bookmark->getUrl()), 'http') | ||
179 | ) { | ||
180 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | ||
181 | $this->container->bookmarkService->set($bookmark, $writeDatastore); | ||
182 | |||
183 | return true; | ||
184 | } | ||
185 | |||
186 | return false; | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * @return string[] Default template variables without values. | ||
191 | */ | ||
192 | protected function initializeTemplateVars(): array | ||
193 | { | ||
194 | return [ | ||
195 | 'previous_page_url' => '', | ||
196 | 'next_page_url' => '', | ||
197 | 'page_max' => '', | ||
198 | 'search_tags' => '', | ||
199 | 'result_count' => '', | ||
200 | ]; | ||
201 | } | ||
202 | |||
203 | /** | ||
204 | * Process legacy routes if necessary. They used query parameters. | ||
205 | * If no legacy routes is passed, return null. | ||
206 | */ | ||
207 | protected function processLegacyController(Request $request, Response $response): ?Response | ||
208 | { | ||
209 | // Legacy smallhash filter | ||
210 | $queryString = $this->container->environment['QUERY_STRING'] ?? null; | ||
211 | if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) { | ||
212 | return $this->redirect($response, '/shaare/' . $match[1]); | ||
213 | } | ||
214 | |||
215 | // Legacy controllers (mostly used for redirections) | ||
216 | if (null !== $request->getQueryParam('do')) { | ||
217 | $legacyController = new LegacyController($this->container); | ||
218 | |||
219 | try { | ||
220 | return $legacyController->process($request, $response, $request->getQueryParam('do')); | ||
221 | } catch (UnknowLegacyRouteException $e) { | ||
222 | // We ignore legacy 404 | ||
223 | return null; | ||
224 | } | ||
225 | } | ||
226 | |||
227 | // Legacy GET admin routes | ||
228 | $legacyGetRoutes = array_intersect( | ||
229 | LegacyController::LEGACY_GET_ROUTES, | ||
230 | array_keys($request->getQueryParams() ?? []) | ||
231 | ); | ||
232 | if (1 === count($legacyGetRoutes)) { | ||
233 | $legacyController = new LegacyController($this->container); | ||
234 | |||
235 | return $legacyController->process($request, $response, $legacyGetRoutes[0]); | ||
236 | } | ||
237 | |||
238 | return null; | ||
239 | } | ||
240 | } | ||
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php new file mode 100644 index 00000000..54a4778f --- /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/FeedController.php b/application/front/controller/visitor/FeedController.php new file mode 100644 index 00000000..da2848c2 --- /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, $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..f17c8ed3 --- /dev/null +++ b/application/front/controller/visitor/ShaarliVisitorController.php | |||
@@ -0,0 +1,171 @@ | |||
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 | foreach ($common_hooks as $name) { | ||
82 | $pluginData = []; | ||
83 | $this->container->pluginManager->executeHooks( | ||
84 | 'render_' . $name, | ||
85 | $pluginData, | ||
86 | [ | ||
87 | 'target' => $template, | ||
88 | 'loggedin' => $this->container->loginManager->isLoggedIn(), | ||
89 | 'basePath' => $this->container->basePath, | ||
90 | ] | ||
91 | ); | ||
92 | $this->assignView('plugins_' . $name, $pluginData); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | protected function executePageHooks(string $hook, array &$data, string $template = null): void | ||
97 | { | ||
98 | $params = [ | ||
99 | 'target' => $template, | ||
100 | 'loggedin' => $this->container->loginManager->isLoggedIn(), | ||
101 | 'basePath' => $this->container->basePath, | ||
102 | ]; | ||
103 | |||
104 | $this->container->pluginManager->executeHooks( | ||
105 | $hook, | ||
106 | $data, | ||
107 | $params | ||
108 | ); | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * Simple helper which prepend the base path to redirect path. | ||
113 | * | ||
114 | * @param Response $response | ||
115 | * @param string $path Absolute path, e.g.: `/`, or `/admin/shaare/123` regardless of install directory | ||
116 | * | ||
117 | * @return Response updated | ||
118 | */ | ||
119 | protected function redirect(Response $response, string $path): Response | ||
120 | { | ||
121 | return $response->withRedirect($this->container->basePath . $path); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Generates a redirection to the previous page, based on the HTTP_REFERER. | ||
126 | * It fails back to the home page. | ||
127 | * | ||
128 | * @param array $loopTerms Terms to remove from path and query string to prevent direction loop. | ||
129 | * @param array $clearParams List of parameter to remove from the query string of the referrer. | ||
130 | */ | ||
131 | protected function redirectFromReferer( | ||
132 | Request $request, | ||
133 | Response $response, | ||
134 | array $loopTerms = [], | ||
135 | array $clearParams = [], | ||
136 | string $anchor = null | ||
137 | ): Response { | ||
138 | $defaultPath = $this->container->basePath . '/'; | ||
139 | $referer = $this->container->environment['HTTP_REFERER'] ?? null; | ||
140 | |||
141 | if (null !== $referer) { | ||
142 | $currentUrl = parse_url($referer); | ||
143 | parse_str($currentUrl['query'] ?? '', $params); | ||
144 | $path = $currentUrl['path'] ?? $defaultPath; | ||
145 | } else { | ||
146 | $params = []; | ||
147 | $path = $defaultPath; | ||
148 | } | ||
149 | |||
150 | // Prevent redirection loop | ||
151 | if (isset($currentUrl)) { | ||
152 | foreach ($clearParams as $value) { | ||
153 | unset($params[$value]); | ||
154 | } | ||
155 | |||
156 | $checkQuery = implode('', array_keys($params)); | ||
157 | foreach ($loopTerms as $value) { | ||
158 | if (strpos($path . $checkQuery, $value) !== false) { | ||
159 | $params = []; | ||
160 | $path = $defaultPath; | ||
161 | break; | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | |||
166 | $queryString = count($params) > 0 ? '?'. http_build_query($params) : ''; | ||
167 | $anchor = $anchor ? '#' . $anchor : ''; | ||
168 | |||
169 | return $response->withRedirect($path . $queryString . $anchor); | ||
170 | } | ||
171 | } | ||
diff --git a/application/front/controller/visitor/TagCloudController.php b/application/front/controller/visitor/TagCloudController.php new file mode 100644 index 00000000..f9c529bc --- /dev/null +++ b/application/front/controller/visitor/TagCloudController.php | |||
@@ -0,0 +1,113 @@ | |||
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 | $searchTags = implode(' ', escape($filteringTags)); | ||
70 | $data = [ | ||
71 | 'search_tags' => $searchTags, | ||
72 | 'tags' => $tags, | ||
73 | ]; | ||
74 | $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type); | ||
75 | $this->assignAllView($data); | ||
76 | |||
77 | $searchTags = !empty($searchTags) ? $searchTags .' - ' : ''; | ||
78 | $this->assignView( | ||
79 | 'pagetitle', | ||
80 | $searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
81 | ); | ||
82 | |||
83 | return $response->write($this->render('tag.' . $type)); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Format the tags array for the tag cloud template. | ||
88 | * | ||
89 | * @param array<string, int> $tags List of tags as key with count as value | ||
90 | * | ||
91 | * @return mixed[] List of tags as key, with count and expected font size in a subarray | ||
92 | */ | ||
93 | protected function formatTagsForCloud(array $tags): array | ||
94 | { | ||
95 | // We sort tags alphabetically, then choose a font size according to count. | ||
96 | // First, find max value. | ||
97 | $maxCount = count($tags) > 0 ? max($tags) : 0; | ||
98 | $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1; | ||
99 | $tagList = []; | ||
100 | foreach ($tags as $key => $value) { | ||
101 | // Tag font size scaling: | ||
102 | // default 15 and 30 logarithm bases affect scaling, | ||
103 | // 2.2 and 0.8 are arbitrary font sizes in em. | ||
104 | $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; | ||
105 | $tagList[$key] = [ | ||
106 | 'count' => $value, | ||
107 | 'size' => number_format($size, 2, '.', ''), | ||
108 | ]; | ||
109 | } | ||
110 | |||
111 | return $tagList; | ||
112 | } | ||
113 | } | ||
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/controllers/LoginController.php b/application/front/controllers/LoginController.php deleted file mode 100644 index ae3599e0..00000000 --- a/application/front/controllers/LoginController.php +++ /dev/null | |||
@@ -1,48 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller; | ||
6 | |||
7 | use Shaarli\Front\Exception\LoginBannedException; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Class LoginController | ||
13 | * | ||
14 | * Slim controller used to render the login page. | ||
15 | * | ||
16 | * The login page is not available if the user is banned | ||
17 | * or if open shaarli setting is enabled. | ||
18 | * | ||
19 | * @package Front\Controller | ||
20 | */ | ||
21 | class LoginController extends ShaarliController | ||
22 | { | ||
23 | public function index(Request $request, Response $response): Response | ||
24 | { | ||
25 | if ($this->container->loginManager->isLoggedIn() | ||
26 | || $this->container->conf->get('security.open_shaarli', false) | ||
27 | ) { | ||
28 | return $response->withRedirect('./'); | ||
29 | } | ||
30 | |||
31 | $userCanLogin = $this->container->loginManager->canLogin($request->getServerParams()); | ||
32 | if ($userCanLogin !== true) { | ||
33 | throw new LoginBannedException(); | ||
34 | } | ||
35 | |||
36 | if ($request->getParam('username') !== null) { | ||
37 | $this->assignView('username', escape($request->getParam('username'))); | ||
38 | } | ||
39 | |||
40 | $this | ||
41 | ->assignView('returnurl', escape($request->getServerParam('HTTP_REFERER'))) | ||
42 | ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true)) | ||
43 | ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) | ||
44 | ; | ||
45 | |||
46 | return $response->write($this->render('loginform')); | ||
47 | } | ||
48 | } | ||
diff --git a/application/front/controllers/ShaarliController.php b/application/front/controllers/ShaarliController.php deleted file mode 100644 index 2b828588..00000000 --- a/application/front/controllers/ShaarliController.php +++ /dev/null | |||
@@ -1,69 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Shaarli\Container\ShaarliContainer; | ||
9 | |||
10 | abstract class ShaarliController | ||
11 | { | ||
12 | /** @var ShaarliContainer */ | ||
13 | protected $container; | ||
14 | |||
15 | /** @param ShaarliContainer $container Slim container (extended for attribute completion). */ | ||
16 | public function __construct(ShaarliContainer $container) | ||
17 | { | ||
18 | $this->container = $container; | ||
19 | } | ||
20 | |||
21 | /** | ||
22 | * Assign variables to RainTPL template through the PageBuilder. | ||
23 | * | ||
24 | * @param mixed $value Value to assign to the template | ||
25 | */ | ||
26 | protected function assignView(string $name, $value): self | ||
27 | { | ||
28 | $this->container->pageBuilder->assign($name, $value); | ||
29 | |||
30 | return $this; | ||
31 | } | ||
32 | |||
33 | protected function render(string $template): string | ||
34 | { | ||
35 | $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL)); | ||
36 | $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
37 | $this->assignView('plugin_errors', $this->container->pluginManager->getErrors()); | ||
38 | |||
39 | $this->executeDefaultHooks($template); | ||
40 | |||
41 | return $this->container->pageBuilder->render($template); | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * Call plugin hooks for header, footer and includes, specifying which page will be rendered. | ||
46 | * Then assign generated data to RainTPL. | ||
47 | */ | ||
48 | protected function executeDefaultHooks(string $template): void | ||
49 | { | ||
50 | $common_hooks = [ | ||
51 | 'includes', | ||
52 | 'header', | ||
53 | 'footer', | ||
54 | ]; | ||
55 | |||
56 | foreach ($common_hooks as $name) { | ||
57 | $plugin_data = []; | ||
58 | $this->container->pluginManager->executeHooks( | ||
59 | 'render_' . $name, | ||
60 | $plugin_data, | ||
61 | [ | ||
62 | 'target' => $template, | ||
63 | 'loggedin' => $this->container->loginManager->isLoggedIn() | ||
64 | ] | ||
65 | ); | ||
66 | $this->assignView('plugins_' . $name, $plugin_data); | ||
67 | } | ||
68 | } | ||
69 | } | ||
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 index b31a4a14..79d0ea15 100644 --- a/application/front/exceptions/LoginBannedException.php +++ b/application/front/exceptions/LoginBannedException.php | |||
@@ -4,7 +4,7 @@ declare(strict_types=1); | |||
4 | 4 | ||
5 | namespace Shaarli\Front\Exception; | 5 | namespace Shaarli\Front\Exception; |
6 | 6 | ||
7 | class LoginBannedException extends ShaarliException | 7 | class LoginBannedException extends ShaarliFrontException |
8 | { | 8 | { |
9 | public function __construct() | 9 | public function __construct() |
10 | { | 10 | { |
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/ShaarliException.php b/application/front/exceptions/ShaarliFrontException.php index 800bfbec..73847e6d 100644 --- a/application/front/exceptions/ShaarliException.php +++ b/application/front/exceptions/ShaarliFrontException.php | |||
@@ -9,11 +9,11 @@ use Throwable; | |||
9 | /** | 9 | /** |
10 | * Class ShaarliException | 10 | * Class ShaarliException |
11 | * | 11 | * |
12 | * Abstract exception class used to defined any custom exception thrown during front rendering. | 12 | * Exception class used to defined any custom exception thrown during front rendering. |
13 | * | 13 | * |
14 | * @package Front\Exception | 14 | * @package Front\Exception |
15 | */ | 15 | */ |
16 | abstract class ShaarliException extends \Exception | 16 | class ShaarliFrontException extends \Exception |
17 | { | 17 | { |
18 | /** Override parent constructor to force $message and $httpCode parameters to be set. */ | 18 | /** Override parent constructor to force $message and $httpCode parameters to be set. */ |
19 | public function __construct(string $message, int $httpCode, Throwable $previous = null) | 19 | public function __construct(string $message, int $httpCode, Throwable $previous = null) |
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..4fc4e3dc 100644 --- a/application/http/HttpUtils.php +++ b/application/http/HttpUtils.php | |||
@@ -369,7 +369,7 @@ function server_url($server) | |||
369 | */ | 369 | */ |
370 | function index_url($server) | 370 | function index_url($server) |
371 | { | 371 | { |
372 | $scriptname = $server['SCRIPT_NAME']; | 372 | $scriptname = $server['SCRIPT_NAME'] ?? ''; |
373 | if (endsWith($scriptname, 'index.php')) { | 373 | if (endsWith($scriptname, 'index.php')) { |
374 | $scriptname = substr($scriptname, 0, -9); | 374 | $scriptname = substr($scriptname, 0, -9); |
375 | } | 375 | } |
@@ -377,7 +377,7 @@ function index_url($server) | |||
377 | } | 377 | } |
378 | 378 | ||
379 | /** | 379 | /** |
380 | * Returns the absolute URL of the current script, with the query | 380 | * Returns the absolute URL of the current script, with current route and query |
381 | * | 381 | * |
382 | * If the resource is "index.php", then it is removed (for better-looking URLs) | 382 | * If the resource is "index.php", then it is removed (for better-looking URLs) |
383 | * | 383 | * |
@@ -387,10 +387,17 @@ function index_url($server) | |||
387 | */ | 387 | */ |
388 | function page_url($server) | 388 | function page_url($server) |
389 | { | 389 | { |
390 | $scriptname = $server['SCRIPT_NAME'] ?? ''; | ||
391 | if (endsWith($scriptname, 'index.php')) { | ||
392 | $scriptname = substr($scriptname, 0, -9); | ||
393 | } | ||
394 | |||
395 | $route = ltrim($server['REQUEST_URI'] ?? '', $scriptname); | ||
390 | if (! empty($server['QUERY_STRING'])) { | 396 | if (! empty($server['QUERY_STRING'])) { |
391 | return index_url($server).'?'.$server['QUERY_STRING']; | 397 | return index_url($server) . $route . '?' . $server['QUERY_STRING']; |
392 | } | 398 | } |
393 | return index_url($server); | 399 | |
400 | return index_url($server) . $route; | ||
394 | } | 401 | } |
395 | 402 | ||
396 | /** | 403 | /** |
@@ -477,3 +484,109 @@ function is_https($server) | |||
477 | 484 | ||
478 | return ! empty($server['HTTPS']); | 485 | return ! empty($server['HTTPS']); |
479 | } | 486 | } |
487 | |||
488 | /** | ||
489 | * Get cURL callback function for CURLOPT_WRITEFUNCTION | ||
490 | * | ||
491 | * @param string $charset to extract from the downloaded page (reference) | ||
492 | * @param string $title to extract from the downloaded page (reference) | ||
493 | * @param string $description to extract from the downloaded page (reference) | ||
494 | * @param string $keywords to extract from the downloaded page (reference) | ||
495 | * @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content | ||
496 | * @param string $curlGetInfo Optionally overrides curl_getinfo function | ||
497 | * | ||
498 | * @return Closure | ||
499 | */ | ||
500 | function get_curl_download_callback( | ||
501 | &$charset, | ||
502 | &$title, | ||
503 | &$description, | ||
504 | &$keywords, | ||
505 | $retrieveDescription, | ||
506 | $curlGetInfo = 'curl_getinfo' | ||
507 | ) { | ||
508 | $isRedirected = false; | ||
509 | $currentChunk = 0; | ||
510 | $foundChunk = null; | ||
511 | |||
512 | /** | ||
513 | * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download). | ||
514 | * | ||
515 | * While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text' | ||
516 | * Then we extract the title and the charset and stop the download when it's done. | ||
517 | * | ||
518 | * @param resource $ch cURL resource | ||
519 | * @param string $data chunk of data being downloaded | ||
520 | * | ||
521 | * @return int|bool length of $data or false if we need to stop the download | ||
522 | */ | ||
523 | return function (&$ch, $data) use ( | ||
524 | $retrieveDescription, | ||
525 | $curlGetInfo, | ||
526 | &$charset, | ||
527 | &$title, | ||
528 | &$description, | ||
529 | &$keywords, | ||
530 | &$isRedirected, | ||
531 | &$currentChunk, | ||
532 | &$foundChunk | ||
533 | ) { | ||
534 | $currentChunk++; | ||
535 | $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); | ||
536 | if (!empty($responseCode) && in_array($responseCode, [301, 302])) { | ||
537 | $isRedirected = true; | ||
538 | return strlen($data); | ||
539 | } | ||
540 | if (!empty($responseCode) && $responseCode !== 200) { | ||
541 | return false; | ||
542 | } | ||
543 | // After a redirection, the content type will keep the previous request value | ||
544 | // until it finds the next content-type header. | ||
545 | if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) { | ||
546 | $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE); | ||
547 | } | ||
548 | if (!empty($contentType) && strpos($contentType, 'text/html') === false) { | ||
549 | return false; | ||
550 | } | ||
551 | if (!empty($contentType) && empty($charset)) { | ||
552 | $charset = header_extract_charset($contentType); | ||
553 | } | ||
554 | if (empty($charset)) { | ||
555 | $charset = html_extract_charset($data); | ||
556 | } | ||
557 | if (empty($title)) { | ||
558 | $title = html_extract_title($data); | ||
559 | $foundChunk = ! empty($title) ? $currentChunk : $foundChunk; | ||
560 | } | ||
561 | if ($retrieveDescription && empty($description)) { | ||
562 | $description = html_extract_tag('description', $data); | ||
563 | $foundChunk = ! empty($description) ? $currentChunk : $foundChunk; | ||
564 | } | ||
565 | if ($retrieveDescription && empty($keywords)) { | ||
566 | $keywords = html_extract_tag('keywords', $data); | ||
567 | if (! empty($keywords)) { | ||
568 | $foundChunk = $currentChunk; | ||
569 | // Keywords use the format tag1, tag2 multiple words, tag | ||
570 | // So we format them to match Shaarli's separator and glue multiple words with '-' | ||
571 | $keywords = implode(' ', array_map(function($keyword) { | ||
572 | return implode('-', preg_split('/\s+/', trim($keyword))); | ||
573 | }, explode(',', $keywords))); | ||
574 | } | ||
575 | } | ||
576 | |||
577 | // We got everything we want, stop the download. | ||
578 | // If we already found either the title, description or keywords, | ||
579 | // it's highly unlikely that we'll found the other metas further than | ||
580 | // in the same chunk of data or the next one. So we also stop the download after that. | ||
581 | if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null | ||
582 | && (! $retrieveDescription | ||
583 | || $foundChunk < $currentChunk | ||
584 | || (!empty($title) && !empty($description) && !empty($keywords)) | ||
585 | ) | ||
586 | ) { | ||
587 | return false; | ||
588 | } | ||
589 | |||
590 | return strlen($data); | ||
591 | }; | ||
592 | } | ||
diff --git a/application/legacy/LegacyController.php b/application/legacy/LegacyController.php new file mode 100644 index 00000000..26465d2c --- /dev/null +++ b/application/legacy/LegacyController.php | |||
@@ -0,0 +1,130 @@ | |||
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 | $parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : ''; | ||
43 | |||
44 | if (!$this->container->loginManager->isLoggedIn()) { | ||
45 | return $this->redirect($response, '/login' . $parameters); | ||
46 | } | ||
47 | |||
48 | return $this->redirect($response, '/admin/shaare' . $parameters); | ||
49 | } | ||
50 | |||
51 | /** Legacy route: ?addlink= */ | ||
52 | protected function addlink(Request $request, Response $response): Response | ||
53 | { | ||
54 | if (!$this->container->loginManager->isLoggedIn()) { | ||
55 | return $this->redirect($response, '/login'); | ||
56 | } | ||
57 | |||
58 | return $this->redirect($response, '/admin/add-shaare'); | ||
59 | } | ||
60 | |||
61 | /** Legacy route: ?do=login */ | ||
62 | protected function login(Request $request, Response $response): Response | ||
63 | { | ||
64 | return $this->redirect($response, '/login'); | ||
65 | } | ||
66 | |||
67 | /** Legacy route: ?do=logout */ | ||
68 | protected function logout(Request $request, Response $response): Response | ||
69 | { | ||
70 | return $this->redirect($response, '/admin/logout'); | ||
71 | } | ||
72 | |||
73 | /** Legacy route: ?do=picwall */ | ||
74 | protected function picwall(Request $request, Response $response): Response | ||
75 | { | ||
76 | return $this->redirect($response, '/picture-wall'); | ||
77 | } | ||
78 | |||
79 | /** Legacy route: ?do=tagcloud */ | ||
80 | protected function tagcloud(Request $request, Response $response): Response | ||
81 | { | ||
82 | return $this->redirect($response, '/tags/cloud'); | ||
83 | } | ||
84 | |||
85 | /** Legacy route: ?do=taglist */ | ||
86 | protected function taglist(Request $request, Response $response): Response | ||
87 | { | ||
88 | return $this->redirect($response, '/tags/list'); | ||
89 | } | ||
90 | |||
91 | /** Legacy route: ?do=daily */ | ||
92 | protected function daily(Request $request, Response $response): Response | ||
93 | { | ||
94 | $dayParam = !empty($request->getParam('day')) ? '?day=' . escape($request->getParam('day')) : ''; | ||
95 | |||
96 | return $this->redirect($response, '/daily' . $dayParam); | ||
97 | } | ||
98 | |||
99 | /** Legacy route: ?do=rss */ | ||
100 | protected function rss(Request $request, Response $response): Response | ||
101 | { | ||
102 | return $this->feed($request, $response, FeedBuilder::$FEED_RSS); | ||
103 | } | ||
104 | |||
105 | /** Legacy route: ?do=atom */ | ||
106 | protected function atom(Request $request, Response $response): Response | ||
107 | { | ||
108 | return $this->feed($request, $response, FeedBuilder::$FEED_ATOM); | ||
109 | } | ||
110 | |||
111 | /** Legacy route: ?do=opensearch */ | ||
112 | protected function opensearch(Request $request, Response $response): Response | ||
113 | { | ||
114 | return $this->redirect($response, '/open-search'); | ||
115 | } | ||
116 | |||
117 | /** Legacy route: ?do=dailyrss */ | ||
118 | protected function dailyrss(Request $request, Response $response): Response | ||
119 | { | ||
120 | return $this->redirect($response, '/daily-rss'); | ||
121 | } | ||
122 | |||
123 | /** Legacy route: ?do=feed */ | ||
124 | protected function feed(Request $request, Response $response, string $feedType): Response | ||
125 | { | ||
126 | $parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : ''; | ||
127 | |||
128 | return $this->redirect($response, '/feed/' . $feedType . $parameters); | ||
129 | } | ||
130 | } | ||
diff --git a/application/legacy/LegacyLinkDB.php b/application/legacy/LegacyLinkDB.php index 7ccf5e54..7bf76fd4 100644 --- a/application/legacy/LegacyLinkDB.php +++ b/application/legacy/LegacyLinkDB.php | |||
@@ -9,6 +9,7 @@ use Iterator; | |||
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 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 bookmarks. | 15 | * Data storage for bookmarks. |
@@ -352,7 +353,8 @@ You use the community supported version of the original Shaarli project, by Seba | |||
352 | 353 | ||
353 | $this->write(); | 354 | $this->write(); |
354 | 355 | ||
355 | invalidateCaches($pageCacheDir); | 356 | $pageCacheManager = new PageCacheManager($pageCacheDir, $this->loggedIn); |
357 | $pageCacheManager->invalidateCaches(); | ||
356 | } | 358 | } |
357 | 359 | ||
358 | /** | 360 | /** |
diff --git a/application/Router.php b/application/legacy/LegacyRouter.php index d7187487..cea99154 100644 --- a/application/Router.php +++ b/application/legacy/LegacyRouter.php | |||
@@ -1,12 +1,15 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli; | 2 | |
3 | namespace Shaarli\Legacy; | ||
3 | 4 | ||
4 | /** | 5 | /** |
5 | * Class Router | 6 | * Class Router |
6 | * | 7 | * |
7 | * (only displayable pages here) | 8 | * (only displayable pages here) |
9 | * | ||
10 | * @deprecated | ||
8 | */ | 11 | */ |
9 | class Router | 12 | class LegacyRouter |
10 | { | 13 | { |
11 | public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update'; | 14 | public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update'; |
12 | 15 | ||
diff --git a/application/legacy/LegacyUpdater.php b/application/legacy/LegacyUpdater.php index 3a5de79f..0ab3a55b 100644 --- a/application/legacy/LegacyUpdater.php +++ b/application/legacy/LegacyUpdater.php | |||
@@ -10,9 +10,9 @@ use ReflectionMethod; | |||
10 | use Shaarli\ApplicationUtils; | 10 | use Shaarli\ApplicationUtils; |
11 | use Shaarli\Bookmark\Bookmark; | 11 | use Shaarli\Bookmark\Bookmark; |
12 | use Shaarli\Bookmark\BookmarkArray; | 12 | use Shaarli\Bookmark\BookmarkArray; |
13 | use Shaarli\Bookmark\LinkDB; | ||
14 | use Shaarli\Bookmark\BookmarkFilter; | 13 | use Shaarli\Bookmark\BookmarkFilter; |
15 | use Shaarli\Bookmark\BookmarkIO; | 14 | use Shaarli\Bookmark\BookmarkIO; |
15 | use Shaarli\Bookmark\LinkDB; | ||
16 | use Shaarli\Config\ConfigJson; | 16 | use Shaarli\Config\ConfigJson; |
17 | use Shaarli\Config\ConfigManager; | 17 | use Shaarli\Config\ConfigManager; |
18 | use Shaarli\Config\ConfigPhp; | 18 | use Shaarli\Config\ConfigPhp; |
@@ -534,7 +534,8 @@ class LegacyUpdater | |||
534 | 534 | ||
535 | if ($thumbnailsEnabled) { | 535 | if ($thumbnailsEnabled) { |
536 | $this->session['warnings'][] = t( | 536 | $this->session['warnings'][] = t( |
537 | 'You have enabled or changed thumbnails mode. <a href="?do=thumbs_update">Please synchronize them</a>.' | 537 | t('You have enabled or changed thumbnails mode.') . |
538 | '<a href="./admin/thumbnails">' . t('Please synchronize them.') . '</a>' | ||
538 | ); | 539 | ); |
539 | } | 540 | } |
540 | 541 | ||
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 d64eef7f..b83f16f8 100644 --- a/application/netscape/NetscapeBookmarkUtils.php +++ b/application/netscape/NetscapeBookmarkUtils.php | |||
@@ -6,6 +6,7 @@ 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\Bookmark; | 11 | use Shaarli\Bookmark\Bookmark; |
11 | use Shaarli\Bookmark\BookmarkServiceInterface; | 12 | use Shaarli\Bookmark\BookmarkServiceInterface; |
@@ -16,10 +17,24 @@ use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser; | |||
16 | 17 | ||
17 | /** | 18 | /** |
18 | * Utilities to import and export bookmarks using the Netscape format | 19 | * Utilities to import and export bookmarks using the Netscape format |
19 | * TODO: Not static, use a container. | ||
20 | */ | 20 | */ |
21 | class NetscapeBookmarkUtils | 21 | class NetscapeBookmarkUtils |
22 | { | 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 | } | ||
23 | 38 | ||
24 | /** | 39 | /** |
25 | * Filters bookmarks and adds Netscape-formatted fields | 40 | * Filters bookmarks and adds Netscape-formatted fields |
@@ -28,18 +43,16 @@ class NetscapeBookmarkUtils | |||
28 | * - timestamp link addition date, using the Unix epoch format | 43 | * - timestamp link addition date, using the Unix epoch format |
29 | * - taglist comma-separated tag list | 44 | * - taglist comma-separated tag list |
30 | * | 45 | * |
31 | * @param BookmarkServiceInterface $bookmarkService Link datastore | ||
32 | * @param BookmarkFormatter $formatter instance | 46 | * @param BookmarkFormatter $formatter instance |
33 | * @param string $selection Which bookmarks to export: (all|private|public) | 47 | * @param string $selection Which bookmarks to export: (all|private|public) |
34 | * @param bool $prependNoteUrl Prepend note permalinks with the server's URL | 48 | * @param bool $prependNoteUrl Prepend note permalinks with the server's URL |
35 | * @param string $indexUrl Absolute URL of the Shaarli index page | 49 | * @param string $indexUrl Absolute URL of the Shaarli index page |
36 | * | 50 | * |
37 | * @return array The bookmarks to be exported, with additional fields | 51 | * @return array The bookmarks to be exported, with additional fields |
38 | *@throws Exception Invalid export selection | ||
39 | * | 52 | * |
53 | * @throws Exception Invalid export selection | ||
40 | */ | 54 | */ |
41 | public static function filterAndFormat( | 55 | public function filterAndFormat( |
42 | $bookmarkService, | ||
43 | $formatter, | 56 | $formatter, |
44 | $selection, | 57 | $selection, |
45 | $prependNoteUrl, | 58 | $prependNoteUrl, |
@@ -51,11 +64,11 @@ class NetscapeBookmarkUtils | |||
51 | } | 64 | } |
52 | 65 | ||
53 | $bookmarkLinks = array(); | 66 | $bookmarkLinks = array(); |
54 | foreach ($bookmarkService->search([], $selection) as $bookmark) { | 67 | foreach ($this->bookmarkService->search([], $selection) as $bookmark) { |
55 | $link = $formatter->format($bookmark); | 68 | $link = $formatter->format($bookmark); |
56 | $link['taglist'] = implode(',', $bookmark->getTags()); | 69 | $link['taglist'] = implode(',', $bookmark->getTags()); |
57 | if ($bookmark->isNote() && $prependNoteUrl) { | 70 | if ($bookmark->isNote() && $prependNoteUrl) { |
58 | $link['url'] = $indexUrl . $link['url']; | 71 | $link['url'] = rtrim($indexUrl, '/') . '/' . ltrim($link['url'], '/'); |
59 | } | 72 | } |
60 | 73 | ||
61 | $bookmarkLinks[] = $link; | 74 | $bookmarkLinks[] = $link; |
@@ -65,60 +78,22 @@ 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 bookmarks were imported | ||
73 | * @param int $overwriteCount how many bookmarks were overwritten | ||
74 | * @param int $skipCount how many bookmarks 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 bookmarks imported, %d bookmarks overwritten, %d bookmarks 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 BookmarkServiceInterface $bookmarkService 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, $bookmarkService, $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 bookmarks? | 99 | // Overwrite existing bookmarks? |
@@ -141,11 +116,11 @@ class NetscapeBookmarkUtils | |||
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', |
@@ -171,7 +146,7 @@ class NetscapeBookmarkUtils | |||
171 | $private = 0; | 146 | $private = 0; |
172 | } | 147 | } |
173 | 148 | ||
174 | $link = $bookmarkService->findByUrl($bkm['uri']); | 149 | $link = $this->bookmarkService->findByUrl($bkm['uri']); |
175 | $existingLink = $link !== null; | 150 | $existingLink = $link !== null; |
176 | if (! $existingLink) { | 151 | if (! $existingLink) { |
177 | $link = new Bookmark(); | 152 | $link = new Bookmark(); |
@@ -193,20 +168,21 @@ class NetscapeBookmarkUtils | |||
193 | } | 168 | } |
194 | 169 | ||
195 | $link->setTitle($bkm['title']); | 170 | $link->setTitle($bkm['title']); |
196 | $link->setUrl($bkm['uri'], $conf->get('security.allowed_protocols')); | 171 | $link->setUrl($bkm['uri'], $this->conf->get('security.allowed_protocols')); |
197 | $link->setDescription($bkm['note']); | 172 | $link->setDescription($bkm['note']); |
198 | $link->setPrivate($private); | 173 | $link->setPrivate($private); |
199 | $link->setTagsString($bkm['tags']); | 174 | $link->setTagsString($bkm['tags']); |
200 | 175 | ||
201 | $bookmarkService->addOrSet($link, false); | 176 | $this->bookmarkService->addOrSet($link, false); |
202 | $importCount++; | 177 | $importCount++; |
203 | } | 178 | } |
204 | 179 | ||
205 | $bookmarkService->save(); | 180 | $this->bookmarkService->save(); |
206 | $history->importLinks(); | 181 | $this->history->importLinks(); |
207 | 182 | ||
208 | $duration = time() - $start; | 183 | $duration = time() - $start; |
209 | return self::importStatus( | 184 | |
185 | return $this->importStatus( | ||
210 | $filename, | 186 | $filename, |
211 | $filesize, | 187 | $filesize, |
212 | $importCount, | 188 | $importCount, |
@@ -215,4 +191,39 @@ class NetscapeBookmarkUtils | |||
215 | $duration | 191 | $duration |
216 | ); | 192 | ); |
217 | } | 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 | } | ||
218 | } | 229 | } |
diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php index f7b24a8e..2d93cb3a 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. |
@@ -108,11 +108,20 @@ class PluginManager | |||
108 | $data['_LOGGEDIN_'] = $params['loggedin']; | 108 | $data['_LOGGEDIN_'] = $params['loggedin']; |
109 | } | 109 | } |
110 | 110 | ||
111 | if (isset($params['basePath'])) { | ||
112 | $data['_BASE_PATH_'] = $params['basePath']; | ||
113 | } | ||
114 | |||
111 | foreach ($this->loadedPlugins as $plugin) { | 115 | foreach ($this->loadedPlugins as $plugin) { |
112 | $hookFunction = $this->buildHookName($hook, $plugin); | 116 | $hookFunction = $this->buildHookName($hook, $plugin); |
113 | 117 | ||
114 | if (function_exists($hookFunction)) { | 118 | if (function_exists($hookFunction)) { |
115 | $data = call_user_func($hookFunction, $data, $this->conf); | 119 | try { |
120 | $data = call_user_func($hookFunction, $data, $this->conf); | ||
121 | } catch (\Throwable $e) { | ||
122 | $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage(); | ||
123 | $this->errors = array_unique(array_merge($this->errors, [$error])); | ||
124 | } | ||
116 | } | 125 | } |
117 | } | 126 | } |
118 | } | 127 | } |
diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php index f4fefda8..7a716673 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\BookmarkServiceInterface; | 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 | /** |
@@ -69,6 +71,15 @@ class PageBuilder | |||
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() |
@@ -136,11 +147,6 @@ 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'])) { | ||
140 | $this->tpl->assign('global_warnings', $_SESSION['warnings']); | ||
141 | unset($_SESSION['warnings']); | ||
142 | } | ||
143 | |||
144 | $this->tpl->assign('formatter', $this->conf->get('formatter', 'default')); | 150 | $this->tpl->assign('formatter', $this->conf->get('formatter', 'default')); |
145 | 151 | ||
146 | // To be removed with a proper theme configuration. | 152 | // To be removed with a proper theme configuration. |
@@ -148,6 +154,34 @@ class PageBuilder | |||
148 | } | 154 | } |
149 | 155 | ||
150 | /** | 156 | /** |
157 | * Affect variable after controller processing. | ||
158 | * Used for alert messages. | ||
159 | */ | ||
160 | protected function finalize(string $basePath): void | ||
161 | { | ||
162 | // TODO: use the SessionManager | ||
163 | $messageKeys = [ | ||
164 | SessionManager::KEY_SUCCESS_MESSAGES, | ||
165 | SessionManager::KEY_WARNING_MESSAGES, | ||
166 | SessionManager::KEY_ERROR_MESSAGES | ||
167 | ]; | ||
168 | foreach ($messageKeys as $messageKey) { | ||
169 | if (!empty($_SESSION[$messageKey])) { | ||
170 | $this->tpl->assign('global_' . $messageKey, $_SESSION[$messageKey]); | ||
171 | unset($_SESSION[$messageKey]); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | $this->assign('base_path', $basePath); | ||
176 | $this->assign( | ||
177 | 'asset_path', | ||
178 | $basePath . '/' . | ||
179 | rtrim($this->conf->get('resource.raintpl_tpl', 'tpl'), '/') . '/' . | ||
180 | $this->conf->get('resource.theme', 'default') | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | /** | ||
151 | * The following assign() method is basically the same as RainTPL (except lazy loading) | 185 | * The following assign() method is basically the same as RainTPL (except lazy loading) |
152 | * | 186 | * |
153 | * @param string $placeholder Template placeholder. | 187 | * @param string $placeholder Template placeholder. |
@@ -185,21 +219,6 @@ class PageBuilder | |||
185 | } | 219 | } |
186 | 220 | ||
187 | /** | 221 | /** |
188 | * Render a specific page (using a template file). | ||
189 | * e.g. $pb->renderPage('picwall'); | ||
190 | * | ||
191 | * @param string $page Template filename (without extension). | ||
192 | */ | ||
193 | public function renderPage($page) | ||
194 | { | ||
195 | if ($this->tpl === false) { | ||
196 | $this->initialize(); | ||
197 | } | ||
198 | |||
199 | $this->tpl->draw($page); | ||
200 | } | ||
201 | |||
202 | /** | ||
203 | * Render a specific page as string (using a template file). | 222 | * Render a specific page as string (using a template file). |
204 | * e.g. $pb->render('picwall'); | 223 | * e.g. $pb->render('picwall'); |
205 | * | 224 | * |
@@ -207,28 +226,14 @@ class PageBuilder | |||
207 | * | 226 | * |
208 | * @return string Processed template content | 227 | * @return string Processed template content |
209 | */ | 228 | */ |
210 | public function render(string $page): string | 229 | public function render(string $page, string $basePath): string |
211 | { | 230 | { |
212 | if ($this->tpl === false) { | 231 | if ($this->tpl === false) { |
213 | $this->initialize(); | 232 | $this->initialize(); |
214 | } | 233 | } |
215 | 234 | ||
216 | return $this->tpl->draw($page, true); | 235 | $this->finalize($basePath); |
217 | } | ||
218 | 236 | ||
219 | /** | 237 | return $this->tpl->draw($page, true); |
220 | * Render a 404 page (uses the template : tpl/404.tpl) | ||
221 | * usage: $PAGE->render404('The link was deleted') | ||
222 | * | ||
223 | * @param string $message A message to display what is not found | ||
224 | */ | ||
225 | public function render404($message = '') | ||
226 | { | ||
227 | if (empty($message)) { | ||
228 | $message = t('The page you are trying to reach does not exist or has been deleted.'); | ||
229 | } | ||
230 | header($_SERVER['SERVER_PROTOCOL'] . ' ' . t('404 Not Found')); | ||
231 | $this->tpl->assign('error_message', $message); | ||
232 | $this->renderPage('404'); | ||
233 | } | 238 | } |
234 | } | 239 | } |
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 39ec9b2e..d74c3118 100644 --- a/application/security/LoginManager.php +++ b/application/security/LoginManager.php | |||
@@ -9,9 +9,6 @@ use Shaarli\Config\ConfigManager; | |||
9 | */ | 9 | */ |
10 | class LoginManager | 10 | class LoginManager |
11 | { | 11 | { |
12 | /** @var string Name of the cookie set after logging in **/ | ||
13 | public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn'; | ||
14 | |||
15 | /** @var array A reference to the $_GLOBALS array */ | 12 | /** @var array A reference to the $_GLOBALS array */ |
16 | protected $globals = []; | 13 | protected $globals = []; |
17 | 14 | ||
@@ -32,17 +29,21 @@ class LoginManager | |||
32 | 29 | ||
33 | /** @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 */ |
34 | protected $staySignedInToken = ''; | 31 | protected $staySignedInToken = ''; |
32 | /** @var CookieManager */ | ||
33 | protected $cookieManager; | ||
35 | 34 | ||
36 | /** | 35 | /** |
37 | * Constructor | 36 | * Constructor |
38 | * | 37 | * |
39 | * @param ConfigManager $configManager Configuration Manager instance | 38 | * @param ConfigManager $configManager Configuration Manager instance |
40 | * @param SessionManager $sessionManager SessionManager instance | 39 | * @param SessionManager $sessionManager SessionManager instance |
40 | * @param CookieManager $cookieManager CookieManager instance | ||
41 | */ | 41 | */ |
42 | public function __construct($configManager, $sessionManager) | 42 | public function __construct($configManager, $sessionManager, $cookieManager) |
43 | { | 43 | { |
44 | $this->configManager = $configManager; | 44 | $this->configManager = $configManager; |
45 | $this->sessionManager = $sessionManager; | 45 | $this->sessionManager = $sessionManager; |
46 | $this->cookieManager = $cookieManager; | ||
46 | $this->banManager = new BanManager( | 47 | $this->banManager = new BanManager( |
47 | $this->configManager->get('security.trusted_proxies', []), | 48 | $this->configManager->get('security.trusted_proxies', []), |
48 | $this->configManager->get('security.ban_after'), | 49 | $this->configManager->get('security.ban_after'), |
@@ -86,10 +87,9 @@ class LoginManager | |||
86 | /** | 87 | /** |
87 | * Check user session state and validity (expiration) | 88 | * Check user session state and validity (expiration) |
88 | * | 89 | * |
89 | * @param array $cookie The $_COOKIE array | ||
90 | * @param string $clientIpId Client IP address identifier | 90 | * @param string $clientIpId Client IP address identifier |
91 | */ | 91 | */ |
92 | public function checkLoginState($cookie, $clientIpId) | 92 | public function checkLoginState($clientIpId) |
93 | { | 93 | { |
94 | if (! $this->configManager->exists('credentials.login')) { | 94 | if (! $this->configManager->exists('credentials.login')) { |
95 | // Shaarli is not configured yet | 95 | // Shaarli is not configured yet |
@@ -97,9 +97,7 @@ class LoginManager | |||
97 | return; | 97 | return; |
98 | } | 98 | } |
99 | 99 | ||
100 | if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE]) | 100 | if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) { |
101 | && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken | ||
102 | ) { | ||
103 | // The user client has a valid stay-signed-in cookie | 101 | // The user client has a valid stay-signed-in cookie |
104 | // Session information is updated with the current client information | 102 | // Session information is updated with the current client information |
105 | $this->sessionManager->storeLoginInfo($clientIpId); | 103 | $this->sessionManager->storeLoginInfo($clientIpId); |
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php index 994fcbe5..76b0afe8 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 | /** |
@@ -202,4 +229,78 @@ class SessionManager | |||
202 | { | 229 | { |
203 | return $this->session; | 230 | return $this->session; |
204 | } | 231 | } |
232 | |||
233 | /** | ||
234 | * @param mixed $default value which will be returned if the $key is undefined | ||
235 | * | ||
236 | * @return mixed Content stored in session | ||
237 | */ | ||
238 | public function getSessionParameter(string $key, $default = null) | ||
239 | { | ||
240 | return $this->session[$key] ?? $default; | ||
241 | } | ||
242 | |||
243 | /** | ||
244 | * Store a variable in user session. | ||
245 | * | ||
246 | * @param string $key Session key | ||
247 | * @param mixed $value Session value to store | ||
248 | * | ||
249 | * @return $this | ||
250 | */ | ||
251 | public function setSessionParameter(string $key, $value): self | ||
252 | { | ||
253 | $this->session[$key] = $value; | ||
254 | |||
255 | return $this; | ||
256 | } | ||
257 | |||
258 | /** | ||
259 | * Store a variable in user session. | ||
260 | * | ||
261 | * @param string $key Session key | ||
262 | * | ||
263 | * @return $this | ||
264 | */ | ||
265 | public function deleteSessionParameter(string $key): self | ||
266 | { | ||
267 | unset($this->session[$key]); | ||
268 | |||
269 | return $this; | ||
270 | } | ||
271 | |||
272 | public function getSavePath(): string | ||
273 | { | ||
274 | return $this->savePath; | ||
275 | } | ||
276 | |||
277 | /* | ||
278 | * Next public functions wrapping native PHP session API. | ||
279 | */ | ||
280 | |||
281 | public function destroy(): bool | ||
282 | { | ||
283 | $this->session = []; | ||
284 | |||
285 | return session_destroy(); | ||
286 | } | ||
287 | |||
288 | public function start(): bool | ||
289 | { | ||
290 | if (session_status() === PHP_SESSION_ACTIVE) { | ||
291 | $this->destroy(); | ||
292 | } | ||
293 | |||
294 | return session_start(); | ||
295 | } | ||
296 | |||
297 | public function cookieParameters(int $lifeTime, string $path, string $domain): bool | ||
298 | { | ||
299 | return session_set_cookie_params($lifeTime, $path, $domain); | ||
300 | } | ||
301 | |||
302 | public function regenerateId(bool $deleteOldSession = false): bool | ||
303 | { | ||
304 | return session_regenerate_id($deleteOldSession); | ||
305 | } | ||
205 | } | 306 | } |
diff --git a/application/updater/Updater.php b/application/updater/Updater.php index 95654d81..88a7bc7b 100644 --- a/application/updater/Updater.php +++ b/application/updater/Updater.php | |||
@@ -2,8 +2,8 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Updater; | 3 | namespace Shaarli\Updater; |
4 | 4 | ||
5 | use Shaarli\Config\ConfigManager; | ||
6 | use Shaarli\Bookmark\BookmarkServiceInterface; | 5 | use Shaarli\Bookmark\BookmarkServiceInterface; |
6 | use Shaarli\Config\ConfigManager; | ||
7 | use Shaarli\Updater\Exception\UpdaterException; | 7 | use Shaarli\Updater\Exception\UpdaterException; |
8 | 8 | ||
9 | /** | 9 | /** |
@@ -21,7 +21,7 @@ class Updater | |||
21 | /** | 21 | /** |
22 | * @var BookmarkServiceInterface instance. | 22 | * @var BookmarkServiceInterface instance. |
23 | */ | 23 | */ |
24 | protected $linkServices; | 24 | protected $bookmarkService; |
25 | 25 | ||
26 | /** | 26 | /** |
27 | * @var ConfigManager $conf Configuration Manager instance. | 27 | * @var ConfigManager $conf Configuration Manager instance. |
@@ -39,6 +39,11 @@ class Updater | |||
39 | protected $methods; | 39 | protected $methods; |
40 | 40 | ||
41 | /** | 41 | /** |
42 | * @var string $basePath Shaarli root directory (from HTTP Request) | ||
43 | */ | ||
44 | protected $basePath = null; | ||
45 | |||
46 | /** | ||
42 | * Object constructor. | 47 | * Object constructor. |
43 | * | 48 | * |
44 | * @param array $doneUpdates Updates which are already done. | 49 | * @param array $doneUpdates Updates which are already done. |
@@ -49,7 +54,7 @@ class Updater | |||
49 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) | 54 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) |
50 | { | 55 | { |
51 | $this->doneUpdates = $doneUpdates; | 56 | $this->doneUpdates = $doneUpdates; |
52 | $this->linkServices = $linkDB; | 57 | $this->bookmarkService = $linkDB; |
53 | $this->conf = $conf; | 58 | $this->conf = $conf; |
54 | $this->isLoggedIn = $isLoggedIn; | 59 | $this->isLoggedIn = $isLoggedIn; |
55 | 60 | ||
@@ -62,13 +67,15 @@ class Updater | |||
62 | * Run all new updates. | 67 | * Run all new updates. |
63 | * 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). |
64 | * | 69 | * |
70 | * @param string $basePath Shaarli root directory (from HTTP Request) | ||
71 | * | ||
65 | * @return array An array containing ran updates. | 72 | * @return array An array containing ran updates. |
66 | * | 73 | * |
67 | * @throws UpdaterException If something went wrong. | 74 | * @throws UpdaterException If something went wrong. |
68 | */ | 75 | */ |
69 | public function update() | 76 | public function update(string $basePath = null) |
70 | { | 77 | { |
71 | $updatesRan = array(); | 78 | $updatesRan = []; |
72 | 79 | ||
73 | // If the user isn't logged in, exit without updating. | 80 | // If the user isn't logged in, exit without updating. |
74 | if ($this->isLoggedIn !== true) { | 81 | if ($this->isLoggedIn !== true) { |
@@ -111,4 +118,62 @@ class Updater | |||
111 | { | 118 | { |
112 | return $this->doneUpdates; | 119 | return $this->doneUpdates; |
113 | } | 120 | } |
121 | |||
122 | public function readUpdates(string $updatesFilepath): array | ||
123 | { | ||
124 | return UpdaterUtils::read_updates_file($updatesFilepath); | ||
125 | } | ||
126 | |||
127 | public function writeUpdates(string $updatesFilepath, array $updates): void | ||
128 | { | ||
129 | UpdaterUtils::write_updates_file($updatesFilepath, $updates); | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * With the Slim routing system, default header link should be `/subfolder/` instead of `?`. | ||
134 | * Otherwise you can not go back to the home page. | ||
135 | * Example: `/subfolder/picture-wall` -> `/subfolder/picture-wall?` instead of `/subfolder/`. | ||
136 | */ | ||
137 | public function updateMethodRelativeHomeLink(): bool | ||
138 | { | ||
139 | if ('?' === trim($this->conf->get('general.header_link'))) { | ||
140 | $this->conf->set('general.header_link', $this->basePath . '/', true, true); | ||
141 | } | ||
142 | |||
143 | return true; | ||
144 | } | ||
145 | |||
146 | /** | ||
147 | * With the Slim routing system, note bookmarks URL formatted `?abcdef` | ||
148 | * should be replaced with `/shaare/abcdef` | ||
149 | */ | ||
150 | public function updateMethodMigrateExistingNotesUrl(): bool | ||
151 | { | ||
152 | $updated = false; | ||
153 | |||
154 | foreach ($this->bookmarkService->search() as $bookmark) { | ||
155 | if ($bookmark->isNote() | ||
156 | && startsWith($bookmark->getUrl(), '?') | ||
157 | && 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match) | ||
158 | ) { | ||
159 | $updated = true; | ||
160 | $bookmark = $bookmark->setUrl('/shaare/' . $match[1]); | ||
161 | |||
162 | $this->bookmarkService->set($bookmark, false); | ||
163 | } | ||
164 | } | ||
165 | |||
166 | if ($updated) { | ||
167 | $this->bookmarkService->save(); | ||
168 | } | ||
169 | |||
170 | return true; | ||
171 | } | ||
172 | |||
173 | public function setBasePath(string $basePath): self | ||
174 | { | ||
175 | $this->basePath = $basePath; | ||
176 | |||
177 | return $this; | ||
178 | } | ||
114 | } | 179 | } |
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..0f29799d 100644 --- a/assets/default/js/base.js +++ b/assets/default/js/base.js | |||
@@ -25,12 +25,16 @@ 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) { |
29 | console.log('refresh'); | ||
29 | const xhr = new XMLHttpRequest(); | 30 | const xhr = new XMLHttpRequest(); |
30 | xhr.open('GET', '?do=token'); | 31 | xhr.open('GET', `${basePath}/admin/token`); |
31 | xhr.onload = () => { | 32 | xhr.onload = () => { |
32 | const token = document.getElementById('token'); | 33 | const elements = document.querySelectorAll('input[name="token"]'); |
33 | token.setAttribute('value', xhr.responseText); | 34 | [...elements].forEach((element) => { |
35 | console.log(element); | ||
36 | element.setAttribute('value', xhr.responseText); | ||
37 | }); | ||
34 | }; | 38 | }; |
35 | xhr.send(); | 39 | xhr.send(); |
36 | } | 40 | } |
@@ -215,6 +219,8 @@ function init(description) { | |||
215 | } | 219 | } |
216 | 220 | ||
217 | (() => { | 221 | (() => { |
222 | const basePath = document.querySelector('input[name="js_base_path"]').value; | ||
223 | |||
218 | /** | 224 | /** |
219 | * Handle responsive menu. | 225 | * Handle responsive menu. |
220 | * Source: http://purecss.io/layouts/tucked-menu-vertical/ | 226 | * Source: http://purecss.io/layouts/tucked-menu-vertical/ |
@@ -461,7 +467,7 @@ function init(description) { | |||
461 | }); | 467 | }); |
462 | 468 | ||
463 | if (window.confirm(message)) { | 469 | if (window.confirm(message)) { |
464 | window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`; | 470 | window.location = `${basePath}/admin/shaare/delete?id=${ids.join('+')}&token=${token.value}`; |
465 | } | 471 | } |
466 | }); | 472 | }); |
467 | } | 473 | } |
@@ -483,7 +489,8 @@ function init(description) { | |||
483 | }); | 489 | }); |
484 | 490 | ||
485 | const ids = links.map(item => item.id); | 491 | const ids = links.map(item => item.id); |
486 | window.location = `?change_visibility&token=${token.value}&newVisibility=${visibility}&ids=${ids.join('+')}`; | 492 | window.location = |
493 | `${basePath}/admin/shaare/visibility?token=${token.value}&newVisibility=${visibility}&id=${ids.join('+')}`; | ||
487 | }); | 494 | }); |
488 | }); | 495 | }); |
489 | } | 496 | } |
@@ -546,7 +553,7 @@ function init(description) { | |||
546 | const refreshedToken = document.getElementById('token').value; | 553 | const refreshedToken = document.getElementById('token').value; |
547 | const fromtag = block.getAttribute('data-tag'); | 554 | const fromtag = block.getAttribute('data-tag'); |
548 | const xhr = new XMLHttpRequest(); | 555 | const xhr = new XMLHttpRequest(); |
549 | xhr.open('POST', '?do=changetag'); | 556 | xhr.open('POST', `${basePath}/admin/tags`); |
550 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 557 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
551 | xhr.onload = () => { | 558 | xhr.onload = () => { |
552 | if (xhr.status !== 200) { | 559 | if (xhr.status !== 200) { |
@@ -558,8 +565,12 @@ function init(description) { | |||
558 | input.setAttribute('value', totag); | 565 | input.setAttribute('value', totag); |
559 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; | 566 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; |
560 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); | 567 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); |
561 | block.querySelector('a.tag-link').setAttribute('href', `?searchtags=${encodeURIComponent(totag)}`); | 568 | block |
562 | block.querySelector('a.rename-tag').setAttribute('href', `?do=changetag&fromtag=${encodeURIComponent(totag)}`); | 569 | .querySelector('a.tag-link') |
570 | .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`); | ||
571 | block | ||
572 | .querySelector('a.rename-tag') | ||
573 | .setAttribute('href', `${basePath}/admin/tags?fromtag=${encodeURIComponent(totag)}`); | ||
563 | 574 | ||
564 | // Refresh awesomplete values | 575 | // Refresh awesomplete values |
565 | existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag)); | 576 | existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag)); |
@@ -567,7 +578,7 @@ function init(description) { | |||
567 | } | 578 | } |
568 | }; | 579 | }; |
569 | xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); | 580 | xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); |
570 | refreshToken(); | 581 | refreshToken(basePath); |
571 | }); | 582 | }); |
572 | }); | 583 | }); |
573 | 584 | ||
@@ -593,13 +604,13 @@ function init(description) { | |||
593 | 604 | ||
594 | if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { | 605 | if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { |
595 | const xhr = new XMLHttpRequest(); | 606 | const xhr = new XMLHttpRequest(); |
596 | xhr.open('POST', '?do=changetag'); | 607 | xhr.open('POST', `${basePath}/admin/tags`); |
597 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 608 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
598 | xhr.onload = () => { | 609 | xhr.onload = () => { |
599 | block.remove(); | 610 | block.remove(); |
600 | }; | 611 | }; |
601 | xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`)); | 612 | xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`)); |
602 | refreshToken(); | 613 | refreshToken(basePath); |
603 | 614 | ||
604 | existingTags = existingTags.filter(tagItem => tagItem !== tag); | 615 | existingTags = existingTags.filter(tagItem => tagItem !== tag); |
605 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | 616 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); |
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss index 243ab1b2..759dff29 100644 --- a/assets/default/scss/shaarli.scss +++ b/assets/default/scss/shaarli.scss | |||
@@ -490,6 +490,10 @@ body, | |||
490 | } | 490 | } |
491 | } | 491 | } |
492 | 492 | ||
493 | .header-alert-message { | ||
494 | text-align: center; | ||
495 | } | ||
496 | |||
493 | // CONTENT - GENERAL | 497 | // CONTENT - GENERAL |
494 | .container { | 498 | .container { |
495 | position: relative; | 499 | position: relative; |
diff --git a/composer.json b/composer.json index 6b670fa2..738d9f58 100644 --- a/composer.json +++ b/composer.json | |||
@@ -53,7 +53,8 @@ | |||
53 | "Shaarli\\Feed\\": "application/feed", | 53 | "Shaarli\\Feed\\": "application/feed", |
54 | "Shaarli\\Formatter\\": "application/formatter", | 54 | "Shaarli\\Formatter\\": "application/formatter", |
55 | "Shaarli\\Front\\": "application/front", | 55 | "Shaarli\\Front\\": "application/front", |
56 | "Shaarli\\Front\\Controller\\": "application/front/controllers", | 56 | "Shaarli\\Front\\Controller\\Admin\\": "application/front/controller/admin", |
57 | "Shaarli\\Front\\Controller\\Visitor\\": "application/front/controller/visitor", | ||
57 | "Shaarli\\Front\\Exception\\": "application/front/exceptions", | 58 | "Shaarli\\Front\\Exception\\": "application/front/exceptions", |
58 | "Shaarli\\Http\\": "application/http", | 59 | "Shaarli\\Http\\": "application/http", |
59 | "Shaarli\\Legacy\\": "application/legacy", | 60 | "Shaarli\\Legacy\\": "application/legacy", |
diff --git a/composer.lock b/composer.lock index b3373a32..ae7a9269 100644 --- a/composer.lock +++ b/composer.lock | |||
@@ -508,16 +508,16 @@ | |||
508 | }, | 508 | }, |
509 | { | 509 | { |
510 | "name": "psr/log", | 510 | "name": "psr/log", |
511 | "version": "1.1.2", | 511 | "version": "1.1.3", |
512 | "source": { | 512 | "source": { |
513 | "type": "git", | 513 | "type": "git", |
514 | "url": "https://github.com/php-fig/log.git", | 514 | "url": "https://github.com/php-fig/log.git", |
515 | "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" | 515 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" |
516 | }, | 516 | }, |
517 | "dist": { | 517 | "dist": { |
518 | "type": "zip", | 518 | "type": "zip", |
519 | "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", | 519 | "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", |
520 | "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", | 520 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", |
521 | "shasum": "" | 521 | "shasum": "" |
522 | }, | 522 | }, |
523 | "require": { | 523 | "require": { |
@@ -551,7 +551,7 @@ | |||
551 | "psr", | 551 | "psr", |
552 | "psr-3" | 552 | "psr-3" |
553 | ], | 553 | ], |
554 | "time": "2019-11-01T11:05:21+00:00" | 554 | "time": "2020-03-23T09:12:05+00:00" |
555 | }, | 555 | }, |
556 | { | 556 | { |
557 | "name": "pubsubhubbub/publisher", | 557 | "name": "pubsubhubbub/publisher", |
@@ -936,24 +936,21 @@ | |||
936 | }, | 936 | }, |
937 | { | 937 | { |
938 | "name": "phpdocumentor/reflection-common", | 938 | "name": "phpdocumentor/reflection-common", |
939 | "version": "2.0.0", | 939 | "version": "2.1.0", |
940 | "source": { | 940 | "source": { |
941 | "type": "git", | 941 | "type": "git", |
942 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", | 942 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", |
943 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" | 943 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" |
944 | }, | 944 | }, |
945 | "dist": { | 945 | "dist": { |
946 | "type": "zip", | 946 | "type": "zip", |
947 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", | 947 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", |
948 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", | 948 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", |
949 | "shasum": "" | 949 | "shasum": "" |
950 | }, | 950 | }, |
951 | "require": { | 951 | "require": { |
952 | "php": ">=7.1" | 952 | "php": ">=7.1" |
953 | }, | 953 | }, |
954 | "require-dev": { | ||
955 | "phpunit/phpunit": "~6" | ||
956 | }, | ||
957 | "type": "library", | 954 | "type": "library", |
958 | "extra": { | 955 | "extra": { |
959 | "branch-alias": { | 956 | "branch-alias": { |
@@ -984,7 +981,7 @@ | |||
984 | "reflection", | 981 | "reflection", |
985 | "static analysis" | 982 | "static analysis" |
986 | ], | 983 | ], |
987 | "time": "2018-08-07T13:53:10+00:00" | 984 | "time": "2020-04-27T09:25:28+00:00" |
988 | }, | 985 | }, |
989 | { | 986 | { |
990 | "name": "phpdocumentor/reflection-docblock", | 987 | "name": "phpdocumentor/reflection-docblock", |
@@ -1087,24 +1084,24 @@ | |||
1087 | }, | 1084 | }, |
1088 | { | 1085 | { |
1089 | "name": "phpspec/prophecy", | 1086 | "name": "phpspec/prophecy", |
1090 | "version": "1.10.1", | 1087 | "version": "v1.10.3", |
1091 | "source": { | 1088 | "source": { |
1092 | "type": "git", | 1089 | "type": "git", |
1093 | "url": "https://github.com/phpspec/prophecy.git", | 1090 | "url": "https://github.com/phpspec/prophecy.git", |
1094 | "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc" | 1091 | "reference": "451c3cd1418cf640de218914901e51b064abb093" |
1095 | }, | 1092 | }, |
1096 | "dist": { | 1093 | "dist": { |
1097 | "type": "zip", | 1094 | "type": "zip", |
1098 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc", | 1095 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", |
1099 | "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc", | 1096 | "reference": "451c3cd1418cf640de218914901e51b064abb093", |
1100 | "shasum": "" | 1097 | "shasum": "" |
1101 | }, | 1098 | }, |
1102 | "require": { | 1099 | "require": { |
1103 | "doctrine/instantiator": "^1.0.2", | 1100 | "doctrine/instantiator": "^1.0.2", |
1104 | "php": "^5.3|^7.0", | 1101 | "php": "^5.3|^7.0", |
1105 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", | 1102 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", |
1106 | "sebastian/comparator": "^1.2.3|^2.0|^3.0", | 1103 | "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", |
1107 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" | 1104 | "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" |
1108 | }, | 1105 | }, |
1109 | "require-dev": { | 1106 | "require-dev": { |
1110 | "phpspec/phpspec": "^2.5 || ^3.2", | 1107 | "phpspec/phpspec": "^2.5 || ^3.2", |
@@ -1146,7 +1143,7 @@ | |||
1146 | "spy", | 1143 | "spy", |
1147 | "stub" | 1144 | "stub" |
1148 | ], | 1145 | ], |
1149 | "time": "2019-12-22T21:05:45+00:00" | 1146 | "time": "2020-03-05T15:02:03+00:00" |
1150 | }, | 1147 | }, |
1151 | { | 1148 | { |
1152 | "name": "phpunit/php-code-coverage", | 1149 | "name": "phpunit/php-code-coverage", |
@@ -1501,12 +1498,12 @@ | |||
1501 | "source": { | 1498 | "source": { |
1502 | "type": "git", | 1499 | "type": "git", |
1503 | "url": "https://github.com/Roave/SecurityAdvisories.git", | 1500 | "url": "https://github.com/Roave/SecurityAdvisories.git", |
1504 | "reference": "67ac6ea8f4a078c3c9b7aec5d7ae70f098c37389" | 1501 | "reference": "5a342e2dc0408d026b97ee3176b5b406e54e3766" |
1505 | }, | 1502 | }, |
1506 | "dist": { | 1503 | "dist": { |
1507 | "type": "zip", | 1504 | "type": "zip", |
1508 | "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/67ac6ea8f4a078c3c9b7aec5d7ae70f098c37389", | 1505 | "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5a342e2dc0408d026b97ee3176b5b406e54e3766", |
1509 | "reference": "67ac6ea8f4a078c3c9b7aec5d7ae70f098c37389", | 1506 | "reference": "5a342e2dc0408d026b97ee3176b5b406e54e3766", |
1510 | "shasum": "" | 1507 | "shasum": "" |
1511 | }, | 1508 | }, |
1512 | "conflict": { | 1509 | "conflict": { |
@@ -1518,11 +1515,17 @@ | |||
1518 | "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", | 1515 | "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", |
1519 | "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", | 1516 | "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", |
1520 | "aws/aws-sdk-php": ">=3,<3.2.1", | 1517 | "aws/aws-sdk-php": ">=3,<3.2.1", |
1518 | "bagisto/bagisto": "<0.1.5", | ||
1519 | "barrelstrength/sprout-base-email": "<3.9", | ||
1520 | "bolt/bolt": "<3.6.10", | ||
1521 | "brightlocal/phpwhois": "<=4.2.5", | 1521 | "brightlocal/phpwhois": "<=4.2.5", |
1522 | "buddypress/buddypress": "<5.1.2", | ||
1522 | "bugsnag/bugsnag-laravel": ">=2,<2.0.2", | 1523 | "bugsnag/bugsnag-laravel": ">=2,<2.0.2", |
1523 | "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", | 1524 | "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", |
1524 | "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", | 1525 | "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", |
1525 | "cartalyst/sentry": "<=2.1.6", | 1526 | "cartalyst/sentry": "<=2.1.6", |
1527 | "centreon/centreon": "<18.10.8|>=19,<19.4.5", | ||
1528 | "cesnet/simplesamlphp-module-proxystatistics": "<3.1", | ||
1526 | "codeigniter/framework": "<=3.0.6", | 1529 | "codeigniter/framework": "<=3.0.6", |
1527 | "composer/composer": "<=1-alpha.11", | 1530 | "composer/composer": "<=1-alpha.11", |
1528 | "contao-components/mediaelement": ">=2.14.2,<2.21.1", | 1531 | "contao-components/mediaelement": ">=2.14.2,<2.21.1", |
@@ -1540,22 +1543,32 @@ | |||
1540 | "doctrine/mongodb-odm": ">=1,<1.0.2", | 1543 | "doctrine/mongodb-odm": ">=1,<1.0.2", |
1541 | "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", | 1544 | "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", |
1542 | "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", | 1545 | "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", |
1546 | "dolibarr/dolibarr": "<=10.0.6", | ||
1543 | "dompdf/dompdf": ">=0.6,<0.6.2", | 1547 | "dompdf/dompdf": ">=0.6,<0.6.2", |
1544 | "drupal/core": ">=7,<7.69|>=8,<8.7.11|>=8.8,<8.8.1", | 1548 | "drupal/core": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", |
1545 | "drupal/drupal": ">=7,<7.69|>=8,<8.7.11|>=8.8,<8.8.1", | 1549 | "drupal/drupal": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", |
1546 | "endroid/qr-code-bundle": "<3.4.2", | 1550 | "endroid/qr-code-bundle": "<3.4.2", |
1551 | "enshrined/svg-sanitize": "<0.13.1", | ||
1547 | "erusev/parsedown": "<1.7.2", | 1552 | "erusev/parsedown": "<1.7.2", |
1548 | "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.4", | 1553 | "ezsystems/demobundle": ">=5.4,<5.4.6.1", |
1549 | "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", | 1554 | "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", |
1550 | "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", | 1555 | "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", |
1556 | "ezsystems/ezplatform": ">=1.7,<1.7.9.1|>=1.13,<1.13.5.1|>=2.5,<2.5.4", | ||
1557 | "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6", | ||
1558 | "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2", | ||
1559 | "ezsystems/ezplatform-user": ">=1,<1.0.1", | ||
1560 | "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.1|>=6,<6.7.9.1|>=6.8,<6.13.6.2|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.6.2", | ||
1561 | "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", | ||
1551 | "ezsystems/repository-forms": ">=2.3,<2.3.2.1", | 1562 | "ezsystems/repository-forms": ">=2.3,<2.3.2.1", |
1552 | "ezyang/htmlpurifier": "<4.1.1", | 1563 | "ezyang/htmlpurifier": "<4.1.1", |
1553 | "firebase/php-jwt": "<2", | 1564 | "firebase/php-jwt": "<2", |
1554 | "fooman/tcpdf": "<6.2.22", | 1565 | "fooman/tcpdf": "<6.2.22", |
1555 | "fossar/tcpdf-parser": "<6.2.22", | 1566 | "fossar/tcpdf-parser": "<6.2.22", |
1567 | "friendsofsymfony/oauth2-php": "<1.3", | ||
1556 | "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", | 1568 | "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", |
1557 | "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", | 1569 | "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", |
1558 | "fuel/core": "<1.8.1", | 1570 | "fuel/core": "<1.8.1", |
1571 | "getgrav/grav": "<1.7-beta.8", | ||
1559 | "gree/jose": "<=2.2", | 1572 | "gree/jose": "<=2.2", |
1560 | "gregwar/rst": "<1.0.3", | 1573 | "gregwar/rst": "<1.0.3", |
1561 | "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", | 1574 | "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", |
@@ -1563,6 +1576,7 @@ | |||
1563 | "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", | 1576 | "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", |
1564 | "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", | 1577 | "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", |
1565 | "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", | 1578 | "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", |
1579 | "illuminate/view": ">=7,<7.1.2", | ||
1566 | "ivankristianto/phpwhois": "<=4.3", | 1580 | "ivankristianto/phpwhois": "<=4.3", |
1567 | "james-heinrich/getid3": "<1.9.9", | 1581 | "james-heinrich/getid3": "<1.9.9", |
1568 | "joomla/session": "<1.3.1", | 1582 | "joomla/session": "<1.3.1", |
@@ -1570,15 +1584,19 @@ | |||
1570 | "kazist/phpwhois": "<=4.2.6", | 1584 | "kazist/phpwhois": "<=4.2.6", |
1571 | "kreait/firebase-php": ">=3.2,<3.8.1", | 1585 | "kreait/firebase-php": ">=3.2,<3.8.1", |
1572 | "la-haute-societe/tcpdf": "<6.2.22", | 1586 | "la-haute-societe/tcpdf": "<6.2.22", |
1573 | "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", | 1587 | "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|>=7,<7.1.2", |
1574 | "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", | 1588 | "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", |
1575 | "league/commonmark": "<0.18.3", | 1589 | "league/commonmark": "<0.18.3", |
1590 | "librenms/librenms": "<1.53", | ||
1591 | "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3", | ||
1576 | "magento/magento1ce": "<1.9.4.3", | 1592 | "magento/magento1ce": "<1.9.4.3", |
1577 | "magento/magento1ee": ">=1,<1.14.4.3", | 1593 | "magento/magento1ee": ">=1,<1.14.4.3", |
1578 | "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", | 1594 | "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", |
1579 | "monolog/monolog": ">=1.8,<1.12", | 1595 | "monolog/monolog": ">=1.8,<1.12", |
1580 | "namshi/jose": "<2.2", | 1596 | "namshi/jose": "<2.2", |
1597 | "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", | ||
1581 | "onelogin/php-saml": "<2.10.4", | 1598 | "onelogin/php-saml": "<2.10.4", |
1599 | "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", | ||
1582 | "openid/php-openid": "<2.3", | 1600 | "openid/php-openid": "<2.3", |
1583 | "oro/crm": ">=1.7,<1.7.4", | 1601 | "oro/crm": ">=1.7,<1.7.4", |
1584 | "oro/platform": ">=1.7,<1.7.4", | 1602 | "oro/platform": ">=1.7,<1.7.4", |
@@ -1587,49 +1605,67 @@ | |||
1587 | "paragonie/random_compat": "<2", | 1605 | "paragonie/random_compat": "<2", |
1588 | "paypal/merchant-sdk-php": "<3.12", | 1606 | "paypal/merchant-sdk-php": "<3.12", |
1589 | "pear/archive_tar": "<1.4.4", | 1607 | "pear/archive_tar": "<1.4.4", |
1608 | "phpfastcache/phpfastcache": ">=5,<5.0.13", | ||
1590 | "phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6", | 1609 | "phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6", |
1591 | "phpoffice/phpexcel": "<=1.8.1", | 1610 | "phpmyadmin/phpmyadmin": "<4.9.2", |
1592 | "phpoffice/phpspreadsheet": "<=1.5", | 1611 | "phpoffice/phpexcel": "<1.8.2", |
1612 | "phpoffice/phpspreadsheet": "<1.8", | ||
1593 | "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", | 1613 | "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", |
1594 | "phpwhois/phpwhois": "<=4.2.5", | 1614 | "phpwhois/phpwhois": "<=4.2.5", |
1595 | "phpxmlrpc/extras": "<0.6.1", | 1615 | "phpxmlrpc/extras": "<0.6.1", |
1616 | "pimcore/pimcore": "<6.3", | ||
1617 | "prestashop/autoupgrade": ">=4,<4.10.1", | ||
1618 | "prestashop/gamification": "<2.3.2", | ||
1619 | "prestashop/ps_facetedsearch": "<3.4.1", | ||
1620 | "privatebin/privatebin": "<1.2.2|>=1.3,<1.3.2", | ||
1596 | "propel/propel": ">=2-alpha.1,<=2-alpha.7", | 1621 | "propel/propel": ">=2-alpha.1,<=2-alpha.7", |
1597 | "propel/propel1": ">=1,<=1.7.1", | 1622 | "propel/propel1": ">=1,<=1.7.1", |
1598 | "pusher/pusher-php-server": "<2.2.1", | 1623 | "pusher/pusher-php-server": "<2.2.1", |
1599 | "robrichards/xmlseclibs": ">=1,<3.0.4", | 1624 | "robrichards/xmlseclibs": "<3.0.4", |
1600 | "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", | 1625 | "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", |
1601 | "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", | 1626 | "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", |
1602 | "sensiolabs/connect": "<4.2.3", | 1627 | "sensiolabs/connect": "<4.2.3", |
1603 | "serluck/phpwhois": "<=4.2.6", | 1628 | "serluck/phpwhois": "<=4.2.6", |
1604 | "shopware/shopware": "<5.3.7", | 1629 | "shopware/shopware": "<5.3.7", |
1605 | "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", | 1630 | "silverstripe/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1", |
1631 | "silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2", | ||
1632 | "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", | ||
1633 | "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", | ||
1606 | "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", | 1634 | "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", |
1607 | "silverstripe/framework": ">=3,<3.6.7|>=3.7,<3.7.3|>=4,<4.4", | 1635 | "silverstripe/framework": "<4.4.5|>=4.5,<4.5.2", |
1608 | "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2", | 1636 | "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2", |
1609 | "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", | 1637 | "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", |
1610 | "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", | 1638 | "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", |
1639 | "silverstripe/subsites": ">=2,<2.1.1", | ||
1640 | "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", | ||
1611 | "silverstripe/userforms": "<3", | 1641 | "silverstripe/userforms": "<3", |
1612 | "simple-updates/phpwhois": "<=1", | 1642 | "simple-updates/phpwhois": "<=1", |
1613 | "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", | 1643 | "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", |
1614 | "simplesamlphp/simplesamlphp": "<1.17.8", | 1644 | "simplesamlphp/simplesamlphp": "<1.18.6", |
1615 | "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", | 1645 | "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", |
1646 | "simplito/elliptic-php": "<1.0.6", | ||
1616 | "slim/slim": "<2.6", | 1647 | "slim/slim": "<2.6", |
1617 | "smarty/smarty": "<3.1.33", | 1648 | "smarty/smarty": "<3.1.33", |
1618 | "socalnick/scn-social-auth": "<1.15.2", | 1649 | "socalnick/scn-social-auth": "<1.15.2", |
1619 | "spoonity/tcpdf": "<6.2.22", | 1650 | "spoonity/tcpdf": "<6.2.22", |
1620 | "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", | 1651 | "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", |
1652 | "ssddanbrown/bookstack": "<0.29.2", | ||
1621 | "stormpath/sdk": ">=0,<9.9.99", | 1653 | "stormpath/sdk": ">=0,<9.9.99", |
1622 | "studio-42/elfinder": "<2.1.48", | 1654 | "studio-42/elfinder": "<2.1.49", |
1623 | "swiftmailer/swiftmailer": ">=4,<5.4.5", | 1655 | "swiftmailer/swiftmailer": ">=4,<5.4.5", |
1624 | "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", | 1656 | "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", |
1625 | "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", | 1657 | "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", |
1626 | "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", | 1658 | "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", |
1627 | "sylius/sylius": ">=1,<1.1.18|>=1.2,<1.2.17|>=1.3,<1.3.12|>=1.4,<1.4.4", | 1659 | "sylius/resource-bundle": "<1.3.13|>=1.4,<1.4.6|>=1.5,<1.5.1|>=1.6,<1.6.3", |
1660 | "sylius/sylius": "<1.3.16|>=1.4,<1.4.12|>=1.5,<1.5.9|>=1.6,<1.6.5", | ||
1661 | "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", | ||
1662 | "symbiote/silverstripe-versionedfiles": "<=2.0.3", | ||
1628 | "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", | 1663 | "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", |
1629 | "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", | 1664 | "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", |
1665 | "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", | ||
1630 | "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", | 1666 | "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", |
1631 | "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", | 1667 | "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", |
1632 | "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", | 1668 | "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", |
1633 | "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", | 1669 | "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", |
1634 | "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", | 1670 | "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", |
1635 | "symfony/mime": ">=4.3,<4.3.8", | 1671 | "symfony/mime": ">=4.3,<4.3.8", |
@@ -1638,14 +1674,14 @@ | |||
1638 | "symfony/polyfill-php55": ">=1,<1.10", | 1674 | "symfony/polyfill-php55": ">=1,<1.10", |
1639 | "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", | 1675 | "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", |
1640 | "symfony/routing": ">=2,<2.0.19", | 1676 | "symfony/routing": ">=2,<2.0.19", |
1641 | "symfony/security": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1677 | "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", |
1642 | "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", | 1678 | "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", |
1643 | "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", | 1679 | "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", |
1644 | "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", | 1680 | "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", |
1645 | "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", | 1681 | "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", |
1646 | "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", | 1682 | "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", |
1647 | "symfony/serializer": ">=2,<2.0.11", | 1683 | "symfony/serializer": ">=2,<2.0.11", |
1648 | "symfony/symfony": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", | 1684 | "symfony/symfony": ">=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", |
1649 | "symfony/translation": ">=2,<2.0.17", | 1685 | "symfony/translation": ">=2,<2.0.17", |
1650 | "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", | 1686 | "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", |
1651 | "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", | 1687 | "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", |
@@ -1658,14 +1694,17 @@ | |||
1658 | "titon/framework": ">=0,<9.9.99", | 1694 | "titon/framework": ">=0,<9.9.99", |
1659 | "truckersmp/phpwhois": "<=4.3.1", | 1695 | "truckersmp/phpwhois": "<=4.3.1", |
1660 | "twig/twig": "<1.38|>=2,<2.7", | 1696 | "twig/twig": "<1.38|>=2,<2.7", |
1661 | "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", | 1697 | "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.17|>=10,<10.4.2", |
1662 | "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", | 1698 | "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.17|>=10,<10.4.2", |
1663 | "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", | 1699 | "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", |
1664 | "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", | 1700 | "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", |
1665 | "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", | 1701 | "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", |
1666 | "ua-parser/uap-php": "<3.8", | 1702 | "ua-parser/uap-php": "<3.8", |
1703 | "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", | ||
1704 | "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", | ||
1667 | "wallabag/tcpdf": "<6.2.22", | 1705 | "wallabag/tcpdf": "<6.2.22", |
1668 | "willdurand/js-translation-bundle": "<2.1.1", | 1706 | "willdurand/js-translation-bundle": "<2.1.1", |
1707 | "yii2mod/yii2-cms": "<1.9.2", | ||
1669 | "yiisoft/yii": ">=1.1.14,<1.1.15", | 1708 | "yiisoft/yii": ">=1.1.14,<1.1.15", |
1670 | "yiisoft/yii2": "<2.0.15", | 1709 | "yiisoft/yii2": "<2.0.15", |
1671 | "yiisoft/yii2-bootstrap": "<2.0.4", | 1710 | "yiisoft/yii2-bootstrap": "<2.0.4", |
@@ -1674,6 +1713,7 @@ | |||
1674 | "yiisoft/yii2-gii": "<2.0.4", | 1713 | "yiisoft/yii2-gii": "<2.0.4", |
1675 | "yiisoft/yii2-jui": "<2.0.4", | 1714 | "yiisoft/yii2-jui": "<2.0.4", |
1676 | "yiisoft/yii2-redis": "<2.0.8", | 1715 | "yiisoft/yii2-redis": "<2.0.8", |
1716 | "yourls/yourls": "<1.7.4", | ||
1677 | "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", | 1717 | "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", |
1678 | "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", | 1718 | "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", |
1679 | "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", | 1719 | "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", |
@@ -1710,10 +1750,15 @@ | |||
1710 | "name": "Marco Pivetta", | 1750 | "name": "Marco Pivetta", |
1711 | "email": "ocramius@gmail.com", | 1751 | "email": "ocramius@gmail.com", |
1712 | "role": "maintainer" | 1752 | "role": "maintainer" |
1753 | }, | ||
1754 | { | ||
1755 | "name": "Ilya Tribusean", | ||
1756 | "email": "slash3b@gmail.com", | ||
1757 | "role": "maintainer" | ||
1713 | } | 1758 | } |
1714 | ], | 1759 | ], |
1715 | "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", | 1760 | "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", |
1716 | "time": "2020-01-06T19:16:46+00:00" | 1761 | "time": "2020-05-12T11:18:47+00:00" |
1717 | }, | 1762 | }, |
1718 | { | 1763 | { |
1719 | "name": "sebastian/code-unit-reverse-lookup", | 1764 | "name": "sebastian/code-unit-reverse-lookup", |
@@ -2326,16 +2371,16 @@ | |||
2326 | }, | 2371 | }, |
2327 | { | 2372 | { |
2328 | "name": "squizlabs/php_codesniffer", | 2373 | "name": "squizlabs/php_codesniffer", |
2329 | "version": "3.5.3", | 2374 | "version": "3.5.5", |
2330 | "source": { | 2375 | "source": { |
2331 | "type": "git", | 2376 | "type": "git", |
2332 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", | 2377 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", |
2333 | "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb" | 2378 | "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6" |
2334 | }, | 2379 | }, |
2335 | "dist": { | 2380 | "dist": { |
2336 | "type": "zip", | 2381 | "type": "zip", |
2337 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/557a1fc7ac702c66b0bbfe16ab3d55839ef724cb", | 2382 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6", |
2338 | "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb", | 2383 | "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6", |
2339 | "shasum": "" | 2384 | "shasum": "" |
2340 | }, | 2385 | }, |
2341 | "require": { | 2386 | "require": { |
@@ -2373,20 +2418,20 @@ | |||
2373 | "phpcs", | 2418 | "phpcs", |
2374 | "standards" | 2419 | "standards" |
2375 | ], | 2420 | ], |
2376 | "time": "2019-12-04T04:46:47+00:00" | 2421 | "time": "2020-04-17T01:09:41+00:00" |
2377 | }, | 2422 | }, |
2378 | { | 2423 | { |
2379 | "name": "symfony/console", | 2424 | "name": "symfony/console", |
2380 | "version": "v4.4.2", | 2425 | "version": "v4.4.8", |
2381 | "source": { | 2426 | "source": { |
2382 | "type": "git", | 2427 | "type": "git", |
2383 | "url": "https://github.com/symfony/console.git", | 2428 | "url": "https://github.com/symfony/console.git", |
2384 | "reference": "82437719dab1e6bdd28726af14cb345c2ec816d0" | 2429 | "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7" |
2385 | }, | 2430 | }, |
2386 | "dist": { | 2431 | "dist": { |
2387 | "type": "zip", | 2432 | "type": "zip", |
2388 | "url": "https://api.github.com/repos/symfony/console/zipball/82437719dab1e6bdd28726af14cb345c2ec816d0", | 2433 | "url": "https://api.github.com/repos/symfony/console/zipball/10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", |
2389 | "reference": "82437719dab1e6bdd28726af14cb345c2ec816d0", | 2434 | "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", |
2390 | "shasum": "" | 2435 | "shasum": "" |
2391 | }, | 2436 | }, |
2392 | "require": { | 2437 | "require": { |
@@ -2449,20 +2494,20 @@ | |||
2449 | ], | 2494 | ], |
2450 | "description": "Symfony Console Component", | 2495 | "description": "Symfony Console Component", |
2451 | "homepage": "https://symfony.com", | 2496 | "homepage": "https://symfony.com", |
2452 | "time": "2019-12-17T10:32:23+00:00" | 2497 | "time": "2020-03-30T11:41:10+00:00" |
2453 | }, | 2498 | }, |
2454 | { | 2499 | { |
2455 | "name": "symfony/finder", | 2500 | "name": "symfony/finder", |
2456 | "version": "v4.4.2", | 2501 | "version": "v4.4.8", |
2457 | "source": { | 2502 | "source": { |
2458 | "type": "git", | 2503 | "type": "git", |
2459 | "url": "https://github.com/symfony/finder.git", | 2504 | "url": "https://github.com/symfony/finder.git", |
2460 | "reference": "ce8743441da64c41e2a667b8eb66070444ed911e" | 2505 | "reference": "5729f943f9854c5781984ed4907bbb817735776b" |
2461 | }, | 2506 | }, |
2462 | "dist": { | 2507 | "dist": { |
2463 | "type": "zip", | 2508 | "type": "zip", |
2464 | "url": "https://api.github.com/repos/symfony/finder/zipball/ce8743441da64c41e2a667b8eb66070444ed911e", | 2509 | "url": "https://api.github.com/repos/symfony/finder/zipball/5729f943f9854c5781984ed4907bbb817735776b", |
2465 | "reference": "ce8743441da64c41e2a667b8eb66070444ed911e", | 2510 | "reference": "5729f943f9854c5781984ed4907bbb817735776b", |
2466 | "shasum": "" | 2511 | "shasum": "" |
2467 | }, | 2512 | }, |
2468 | "require": { | 2513 | "require": { |
@@ -2498,20 +2543,20 @@ | |||
2498 | ], | 2543 | ], |
2499 | "description": "Symfony Finder Component", | 2544 | "description": "Symfony Finder Component", |
2500 | "homepage": "https://symfony.com", | 2545 | "homepage": "https://symfony.com", |
2501 | "time": "2019-11-17T21:56:56+00:00" | 2546 | "time": "2020-03-27T16:54:36+00:00" |
2502 | }, | 2547 | }, |
2503 | { | 2548 | { |
2504 | "name": "symfony/polyfill-ctype", | 2549 | "name": "symfony/polyfill-ctype", |
2505 | "version": "v1.13.1", | 2550 | "version": "v1.17.0", |
2506 | "source": { | 2551 | "source": { |
2507 | "type": "git", | 2552 | "type": "git", |
2508 | "url": "https://github.com/symfony/polyfill-ctype.git", | 2553 | "url": "https://github.com/symfony/polyfill-ctype.git", |
2509 | "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" | 2554 | "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" |
2510 | }, | 2555 | }, |
2511 | "dist": { | 2556 | "dist": { |
2512 | "type": "zip", | 2557 | "type": "zip", |
2513 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", | 2558 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", |
2514 | "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", | 2559 | "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", |
2515 | "shasum": "" | 2560 | "shasum": "" |
2516 | }, | 2561 | }, |
2517 | "require": { | 2562 | "require": { |
@@ -2523,7 +2568,7 @@ | |||
2523 | "type": "library", | 2568 | "type": "library", |
2524 | "extra": { | 2569 | "extra": { |
2525 | "branch-alias": { | 2570 | "branch-alias": { |
2526 | "dev-master": "1.13-dev" | 2571 | "dev-master": "1.17-dev" |
2527 | } | 2572 | } |
2528 | }, | 2573 | }, |
2529 | "autoload": { | 2574 | "autoload": { |
@@ -2556,20 +2601,20 @@ | |||
2556 | "polyfill", | 2601 | "polyfill", |
2557 | "portable" | 2602 | "portable" |
2558 | ], | 2603 | ], |
2559 | "time": "2019-11-27T13:56:44+00:00" | 2604 | "time": "2020-05-12T16:14:59+00:00" |
2560 | }, | 2605 | }, |
2561 | { | 2606 | { |
2562 | "name": "symfony/polyfill-mbstring", | 2607 | "name": "symfony/polyfill-mbstring", |
2563 | "version": "v1.13.1", | 2608 | "version": "v1.17.0", |
2564 | "source": { | 2609 | "source": { |
2565 | "type": "git", | 2610 | "type": "git", |
2566 | "url": "https://github.com/symfony/polyfill-mbstring.git", | 2611 | "url": "https://github.com/symfony/polyfill-mbstring.git", |
2567 | "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" | 2612 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" |
2568 | }, | 2613 | }, |
2569 | "dist": { | 2614 | "dist": { |
2570 | "type": "zip", | 2615 | "type": "zip", |
2571 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", | 2616 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", |
2572 | "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", | 2617 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", |
2573 | "shasum": "" | 2618 | "shasum": "" |
2574 | }, | 2619 | }, |
2575 | "require": { | 2620 | "require": { |
@@ -2581,7 +2626,7 @@ | |||
2581 | "type": "library", | 2626 | "type": "library", |
2582 | "extra": { | 2627 | "extra": { |
2583 | "branch-alias": { | 2628 | "branch-alias": { |
2584 | "dev-master": "1.13-dev" | 2629 | "dev-master": "1.17-dev" |
2585 | } | 2630 | } |
2586 | }, | 2631 | }, |
2587 | "autoload": { | 2632 | "autoload": { |
@@ -2615,20 +2660,20 @@ | |||
2615 | "portable", | 2660 | "portable", |
2616 | "shim" | 2661 | "shim" |
2617 | ], | 2662 | ], |
2618 | "time": "2019-11-27T14:18:11+00:00" | 2663 | "time": "2020-05-12T16:47:27+00:00" |
2619 | }, | 2664 | }, |
2620 | { | 2665 | { |
2621 | "name": "symfony/polyfill-php73", | 2666 | "name": "symfony/polyfill-php73", |
2622 | "version": "v1.13.1", | 2667 | "version": "v1.17.0", |
2623 | "source": { | 2668 | "source": { |
2624 | "type": "git", | 2669 | "type": "git", |
2625 | "url": "https://github.com/symfony/polyfill-php73.git", | 2670 | "url": "https://github.com/symfony/polyfill-php73.git", |
2626 | "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" | 2671 | "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" |
2627 | }, | 2672 | }, |
2628 | "dist": { | 2673 | "dist": { |
2629 | "type": "zip", | 2674 | "type": "zip", |
2630 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", | 2675 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", |
2631 | "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", | 2676 | "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", |
2632 | "shasum": "" | 2677 | "shasum": "" |
2633 | }, | 2678 | }, |
2634 | "require": { | 2679 | "require": { |
@@ -2637,7 +2682,7 @@ | |||
2637 | "type": "library", | 2682 | "type": "library", |
2638 | "extra": { | 2683 | "extra": { |
2639 | "branch-alias": { | 2684 | "branch-alias": { |
2640 | "dev-master": "1.13-dev" | 2685 | "dev-master": "1.17-dev" |
2641 | } | 2686 | } |
2642 | }, | 2687 | }, |
2643 | "autoload": { | 2688 | "autoload": { |
@@ -2673,7 +2718,7 @@ | |||
2673 | "portable", | 2718 | "portable", |
2674 | "shim" | 2719 | "shim" |
2675 | ], | 2720 | ], |
2676 | "time": "2019-11-27T16:25:15+00:00" | 2721 | "time": "2020-05-12T16:47:27+00:00" |
2677 | }, | 2722 | }, |
2678 | { | 2723 | { |
2679 | "name": "symfony/service-contracts", | 2724 | "name": "symfony/service-contracts", |
@@ -2815,16 +2860,16 @@ | |||
2815 | }, | 2860 | }, |
2816 | { | 2861 | { |
2817 | "name": "webmozart/assert", | 2862 | "name": "webmozart/assert", |
2818 | "version": "1.6.0", | 2863 | "version": "1.8.0", |
2819 | "source": { | 2864 | "source": { |
2820 | "type": "git", | 2865 | "type": "git", |
2821 | "url": "https://github.com/webmozart/assert.git", | 2866 | "url": "https://github.com/webmozart/assert.git", |
2822 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" | 2867 | "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" |
2823 | }, | 2868 | }, |
2824 | "dist": { | 2869 | "dist": { |
2825 | "type": "zip", | 2870 | "type": "zip", |
2826 | "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", | 2871 | "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", |
2827 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", | 2872 | "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", |
2828 | "shasum": "" | 2873 | "shasum": "" |
2829 | }, | 2874 | }, |
2830 | "require": { | 2875 | "require": { |
@@ -2832,7 +2877,7 @@ | |||
2832 | "symfony/polyfill-ctype": "^1.8" | 2877 | "symfony/polyfill-ctype": "^1.8" |
2833 | }, | 2878 | }, |
2834 | "conflict": { | 2879 | "conflict": { |
2835 | "vimeo/psalm": "<3.6.0" | 2880 | "vimeo/psalm": "<3.9.1" |
2836 | }, | 2881 | }, |
2837 | "require-dev": { | 2882 | "require-dev": { |
2838 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" | 2883 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" |
@@ -2859,7 +2904,7 @@ | |||
2859 | "check", | 2904 | "check", |
2860 | "validate" | 2905 | "validate" |
2861 | ], | 2906 | ], |
2862 | "time": "2019-11-24T13:36:37+00:00" | 2907 | "time": "2020-04-18T12:12:48+00:00" |
2863 | } | 2908 | } |
2864 | ], | 2909 | ], |
2865 | "aliases": [], | 2910 | "aliases": [], |
diff --git a/doc/md/Plugin-System.md b/doc/md/Plugin-System.md index d5b16e2d..f264e873 100644 --- a/doc/md/Plugin-System.md +++ b/doc/md/Plugin-System.md | |||
@@ -131,7 +131,7 @@ If it's still not working, please [open an issue](https://github.com/shaarli/Sha | |||
131 | | ------------- |:-------------:| | 131 | | ------------- |:-------------:| |
132 | | [render_header](#render_header) | Allow plugin to add content in page headers. | | 132 | | [render_header](#render_header) | Allow plugin to add content in page headers. | |
133 | | [render_includes](#render_includes) | Allow plugin to include their own CSS files. | | 133 | | [render_includes](#render_includes) | Allow plugin to include their own CSS files. | |
134 | | [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. | | 134 | | [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. | |
135 | | [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. | | 135 | | [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. | |
136 | | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. | | 136 | | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. | |
137 | | [render_tools](#render_tools) | Allow to add content at the end of the page. | | 137 | | [render_tools](#render_tools) | Allow to add content at the end of the page. | |
@@ -515,7 +515,7 @@ Otherwise, you can use your own JS as long as this field is send by the form: | |||
515 | 515 | ||
516 | ### Placeholder system | 516 | ### Placeholder system |
517 | 517 | ||
518 | In order to make plugins work with every custom themes, you need to add variable placeholder in your templates. | 518 | In order to make plugins work with every custom themes, you need to add variable placeholder in your templates. |
519 | 519 | ||
520 | It's a RainTPL loop like this: | 520 | It's a RainTPL loop like this: |
521 | 521 | ||
@@ -537,7 +537,7 @@ At the end of the menu: | |||
537 | 537 | ||
538 | At the end of file, before clearing floating blocks: | 538 | At the end of file, before clearing floating blocks: |
539 | 539 | ||
540 | {if="!empty($plugin_errors) && isLoggedIn()"} | 540 | {if="!empty($plugin_errors) && $is_logged_in"} |
541 | <ul class="errors"> | 541 | <ul class="errors"> |
542 | {loop="plugin_errors"} | 542 | {loop="plugin_errors"} |
543 | <li>{$value}</li> | 543 | <li>{$value}</li> |
diff --git a/doc/md/RSS-feeds.md b/doc/md/RSS-feeds.md index d943218e..ecbff09a 100644 --- a/doc/md/RSS-feeds.md +++ b/doc/md/RSS-feeds.md | |||
@@ -1,14 +1,14 @@ | |||
1 | ### Feeds options | 1 | ### Feeds options |
2 | 2 | ||
3 | Feeds are available in ATOM with `?do=atom` and RSS with `do=RSS`. | 3 | Feeds are available in ATOM with `/feed/atom` and RSS with `/feed/rss`. |
4 | 4 | ||
5 | Options: | 5 | Options: |
6 | 6 | ||
7 | - You can use `permalinks` in the feed URL to get permalink to Shaares instead of direct link to shaared URL. | 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`. | 8 | - E.G. `https://my.shaarli.domain/feed/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. | 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` | 10 | - `https://my.shaarli.domain/feed/atom?permalinks&nb=42` |
11 | - `https://my.shaarli.domain/?do=atom&permalinks&nb=all` | 11 | - `https://my.shaarli.domain/feed/atom?permalinks&nb=all` |
12 | 12 | ||
13 | ### RSS Feeds or Picture Wall for a specific search/tag | 13 | ### RSS Feeds or Picture Wall for a specific search/tag |
14 | 14 | ||
@@ -21,8 +21,8 @@ For example, if you want to subscribe only to links tagged `photography`: | |||
21 | - Click on the `RSS Feed` button. | 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. | 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`?) | 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: | 24 | - You can also build the URLs manually: |
25 | - `https://my.shaarli.domain/?do=rss&searchtags=nature` | 25 | - `https://my.shaarli.domain/?do=rss&searchtags=nature` |
26 | - `https://my.shaarli.domain/links/?do=picwall&searchterm=poney` | 26 | - `https://my.shaarli.domain/links/picture-wall?searchterm=poney` |
27 | 27 | ||
28 | ![](images/rss-filter-1.png) ![](images/rss-filter-2.png) | 28 | ![](images/rss-filter-1.png) ![](images/rss-filter-2.png) |
diff --git a/doc/md/Translations.md b/doc/md/Translations.md index 58b92da3..c23ec962 100644 --- a/doc/md/Translations.md +++ b/doc/md/Translations.md | |||
@@ -32,20 +32,20 @@ Here is a list : | |||
32 | ``` | 32 | ``` |
33 | http://<replace_domain>/ | 33 | http://<replace_domain>/ |
34 | http://<replace_domain>/?nonope | 34 | http://<replace_domain>/?nonope |
35 | http://<replace_domain>/?do=addlink | 35 | http://<replace_domain>/admin/add-shaare |
36 | http://<replace_domain>/?do=changepasswd | 36 | http://<replace_domain>/admin/password |
37 | http://<replace_domain>/?do=changetag | 37 | http://<replace_domain>/admin/tags |
38 | http://<replace_domain>/?do=configure | 38 | http://<replace_domain>/admin/configure |
39 | http://<replace_domain>/?do=tools | 39 | http://<replace_domain>/admin/tools |
40 | http://<replace_domain>/?do=daily | 40 | http://<replace_domain>/daily |
41 | http://<replace_domain>/?post | 41 | http://<replace_domain>/admin/shaare |
42 | http://<replace_domain>/?do=export | 42 | http://<replace_domain>/admin/export |
43 | http://<replace_domain>/?do=import | 43 | http://<replace_domain>/admin/import |
44 | http://<replace_domain>/login | 44 | http://<replace_domain>/login |
45 | http://<replace_domain>/?do=picwall | 45 | http://<replace_domain>/picture-wall |
46 | http://<replace_domain>/?do=pluginadmin | 46 | http://<replace_domain>/admin/plugins |
47 | http://<replace_domain>/?do=tagcloud | 47 | http://<replace_domain>/tags/cloud |
48 | http://<replace_domain>/?do=taglist | 48 | http://<replace_domain>/tags/list |
49 | ``` | 49 | ``` |
50 | 50 | ||
51 | #### Improve existing translation | 51 | #### Improve existing translation |
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index 026d0101..fbb2fe64 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-08-27 12:01+0200\n" |
5 | "PO-Revision-Date: 2019-07-13 10:49+0200\n" | 5 | "PO-Revision-Date: 2020-08-27 12:02+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 | ||
@@ -77,50 +79,61 @@ msgstr "" | |||
77 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " | 79 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " |
78 | "miniatures sont désormais désactivées. Rechargez la page." | 80 | "miniatures sont désormais désactivées. Rechargez la page." |
79 | 81 | ||
80 | #: application/Utils.php:379 tests/UtilsTest.php:343 | 82 | #: application/Utils.php:383 |
81 | msgid "Setting not set" | 83 | msgid "Setting not set" |
82 | msgstr "Paramètre non défini" | 84 | msgstr "Paramètre non défini" |
83 | 85 | ||
84 | #: application/Utils.php:386 tests/UtilsTest.php:341 tests/UtilsTest.php:342 | 86 | #: application/Utils.php:390 |
85 | msgid "Unlimited" | 87 | msgid "Unlimited" |
86 | msgstr "Illimité" | 88 | msgstr "Illimité" |
87 | 89 | ||
88 | #: application/Utils.php:389 tests/UtilsTest.php:338 tests/UtilsTest.php:339 | 90 | #: application/Utils.php:393 |
89 | #: tests/UtilsTest.php:353 | ||
90 | msgid "B" | 91 | msgid "B" |
91 | msgstr "o" | 92 | msgstr "o" |
92 | 93 | ||
93 | #: application/Utils.php:389 tests/UtilsTest.php:332 tests/UtilsTest.php:333 | 94 | #: application/Utils.php:393 |
94 | #: tests/UtilsTest.php:340 | ||
95 | msgid "kiB" | 95 | msgid "kiB" |
96 | msgstr "ko" | 96 | msgstr "ko" |
97 | 97 | ||
98 | #: application/Utils.php:389 tests/UtilsTest.php:334 tests/UtilsTest.php:335 | 98 | #: application/Utils.php:393 |
99 | #: tests/UtilsTest.php:351 tests/UtilsTest.php:352 | ||
100 | msgid "MiB" | 99 | msgid "MiB" |
101 | msgstr "Mo" | 100 | msgstr "Mo" |
102 | 101 | ||
103 | #: application/Utils.php:389 tests/UtilsTest.php:336 tests/UtilsTest.php:337 | 102 | #: application/Utils.php:393 |
104 | msgid "GiB" | 103 | msgid "GiB" |
105 | msgstr "Go" | 104 | msgstr "Go" |
106 | 105 | ||
107 | #: application/bookmark/LinkDB.php:128 | 106 | #: application/bookmark/BookmarkFileService.php:165 |
108 | msgid "You are not authorized to add a link." | 107 | #: application/bookmark/BookmarkFileService.php:190 |
109 | msgstr "Vous n'êtes pas autorisé à ajouter un lien." | 108 | #: application/bookmark/BookmarkFileService.php:215 |
110 | 109 | #: application/bookmark/BookmarkFileService.php:232 | |
111 | #: application/bookmark/LinkDB.php:131 | 110 | msgid "You're not authorized to alter the datastore" |
112 | msgid "Internal Error: A link should always have an id and URL." | 111 | msgstr "Vous n'êtes pas autorisé à modifier les données" |
113 | msgstr "Erreur interne : un lien devrait toujours avoir un ID et une URL." | 112 | |
114 | 113 | #: application/bookmark/BookmarkFileService.php:168 | |
115 | #: application/bookmark/LinkDB.php:134 | 114 | #: application/bookmark/BookmarkFileService.php:193 |
116 | msgid "You must specify an integer as a key." | 115 | #: application/bookmark/BookmarkFileService.php:235 |
117 | msgstr "Vous devez utiliser un entier comme clé." | 116 | msgid "Provided data is invalid" |
117 | msgstr "Les informations fournies ne sont pas valides" | ||
118 | |||
119 | #: application/bookmark/BookmarkFileService.php:196 | ||
120 | msgid "This bookmarks already exists" | ||
121 | msgstr "Ce marque-page existe déjà ." | ||
122 | |||
123 | #: application/bookmark/BookmarkInitializer.php:37 | ||
124 | #: application/legacy/LegacyLinkDB.php:266 | ||
125 | msgid "My secret stuff... - Pastebin.com" | ||
126 | msgstr "Mes trucs secrets... - Pastebin.com" | ||
118 | 127 | ||
119 | #: application/bookmark/LinkDB.php:137 | 128 | #: application/bookmark/BookmarkInitializer.php:39 |
120 | msgid "Array offset and link ID must be equal." | 129 | #: application/legacy/LegacyLinkDB.php:268 |
121 | msgstr "La clé du tableau et l'ID du lien doivent être identiques." | 130 | msgid "Shhhh! I'm a private link only YOU can see. You can delete me too." |
131 | msgstr "" | ||
132 | "Pssst ! Je suis un lien privé que VOUS êtes le seul à voir. Vous pouvez me " | ||
133 | "supprimer aussi." | ||
122 | 134 | ||
123 | #: application/bookmark/LinkDB.php:243 | 135 | #: application/bookmark/BookmarkInitializer.php:45 |
136 | #: application/legacy/LegacyLinkDB.php:246 | ||
124 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | 137 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 |
125 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 | 138 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 |
126 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15 | 139 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15 |
@@ -131,7 +144,8 @@ msgstr "" | |||
131 | "Le gestionnaire de marque-pages personnel, minimaliste, et sans base de " | 144 | "Le gestionnaire de marque-pages personnel, minimaliste, et sans base de " |
132 | "données" | 145 | "données" |
133 | 146 | ||
134 | #: application/bookmark/LinkDB.php:246 | 147 | #: application/bookmark/BookmarkInitializer.php:48 |
148 | #: application/legacy/LegacyLinkDB.php:249 | ||
135 | msgid "" | 149 | msgid "" |
136 | "Welcome to Shaarli! This is your first public bookmark. To edit or delete " | 150 | "Welcome to Shaarli! This is your first public bookmark. To edit or delete " |
137 | "me, you must first login.\n" | 151 | "me, you must first login.\n" |
@@ -151,17 +165,7 @@ msgstr "" | |||
151 | "Vous utilisez la version supportée par la communauté du projet original " | 165 | "Vous utilisez la version supportée par la communauté du projet original " |
152 | "Shaarli de Sébastien Sauvage." | 166 | "Shaarli de Sébastien Sauvage." |
153 | 167 | ||
154 | #: application/bookmark/LinkDB.php:263 | 168 | #: application/bookmark/exception/BookmarkNotFoundException.php:13 |
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 "" | ||
161 | "Pssst ! Je suis un lien privé que VOUS êtes le seul à voir. Vous pouvez me " | ||
162 | "supprimer aussi." | ||
163 | |||
164 | #: application/bookmark/exception/LinkNotFoundException.php:13 | ||
165 | msgid "The link you are trying to reach does not exist or has been deleted." | 169 | 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é." | 170 | msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé." |
167 | 171 | ||
@@ -173,8 +177,8 @@ msgstr "" | |||
173 | "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que " | 177 | "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é." | 178 | "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." |
175 | 179 | ||
176 | #: application/config/ConfigManager.php:135 | 180 | #: application/config/ConfigManager.php:136 |
177 | #: application/config/ConfigManager.php:162 | 181 | #: application/config/ConfigManager.php:163 |
178 | msgid "Invalid setting key parameter. String expected, got: " | 182 | msgid "Invalid setting key parameter. String expected, got: " |
179 | msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " | 183 | msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " |
180 | 184 | ||
@@ -196,268 +200,346 @@ msgstr "Vous n'êtes pas autorisé à modifier la configuration." | |||
196 | msgid "Error accessing" | 200 | msgid "Error accessing" |
197 | msgstr "Une erreur s'est produite en accédant à " | 201 | msgstr "Une erreur s'est produite en accédant à " |
198 | 202 | ||
199 | #: application/feed/Cache.php:16 | 203 | #: 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" | 204 | msgid "Direct link" |
206 | msgstr "Liens directs" | 205 | msgstr "Liens directs" |
207 | 206 | ||
208 | #: application/feed/FeedBuilder.php:157 | 207 | #: application/feed/FeedBuilder.php:181 |
209 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 | 208 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 |
210 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177 | 209 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 |
211 | msgid "Permalink" | 210 | msgid "Permalink" |
212 | msgstr "Permalien" | 211 | msgstr "Permalien" |
213 | 212 | ||
214 | #: application/netscape/NetscapeBookmarkUtils.php:42 | 213 | #: application/front/controller/admin/ConfigureController.php:54 |
215 | msgid "Invalid export selection:" | 214 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 |
216 | msgstr "Sélection d'export invalide :" | 215 | msgid "Configure" |
216 | msgstr "Configurer" | ||
217 | 217 | ||
218 | #: application/netscape/NetscapeBookmarkUtils.php:87 | 218 | #: application/front/controller/admin/ConfigureController.php:102 |
219 | #, php-format | 219 | #: application/legacy/LegacyUpdater.php:537 |
220 | msgid "File %s (%d bytes) " | 220 | msgid "You have enabled or changed thumbnails mode." |
221 | msgstr "Le fichier %s (%d octets) " | 221 | msgstr "Vous avez activé ou changé le mode de miniatures." |
222 | 222 | ||
223 | #: application/netscape/NetscapeBookmarkUtils.php:89 | 223 | #: application/front/controller/admin/ConfigureController.php:103 |
224 | msgid "has an unknown file format. Nothing was imported." | 224 | #: application/legacy/LegacyUpdater.php:538 |
225 | msgstr "a un format inconnu. Rien n'a été importé." | 225 | msgid "Please synchronize them." |
226 | msgstr "Merci de les synchroniser." | ||
227 | |||
228 | #: application/front/controller/admin/ConfigureController.php:113 | ||
229 | #: application/front/controller/visitor/InstallController.php:136 | ||
230 | msgid "Error while writing config file after configuration update." | ||
231 | msgstr "" | ||
232 | "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." | ||
233 | |||
234 | #: application/front/controller/admin/ConfigureController.php:122 | ||
235 | msgid "Configuration was saved." | ||
236 | msgstr "La configuration a été sauvegardée." | ||
237 | |||
238 | #: application/front/controller/admin/ExportController.php:26 | ||
239 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64 | ||
240 | msgid "Export" | ||
241 | msgstr "Exporter" | ||
226 | 242 | ||
227 | #: application/netscape/NetscapeBookmarkUtils.php:93 | 243 | #: application/front/controller/admin/ExportController.php:42 |
244 | msgid "Please select an export mode." | ||
245 | msgstr "Merci de choisir un mode d'export." | ||
246 | |||
247 | #: application/front/controller/admin/ImportController.php:41 | ||
248 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 | ||
249 | msgid "Import" | ||
250 | msgstr "Importer" | ||
251 | |||
252 | #: application/front/controller/admin/ImportController.php:55 | ||
253 | msgid "No import file provided." | ||
254 | msgstr "Aucun fichier à importer n'a été fourni." | ||
255 | |||
256 | #: application/front/controller/admin/ImportController.php:66 | ||
228 | #, php-format | 257 | #, php-format |
229 | msgid "" | 258 | msgid "" |
230 | "was successfully processed in %d seconds: %d links imported, %d links " | 259 | "The file you are trying to upload is probably bigger than what this " |
231 | "overwritten, %d links skipped." | 260 | "webserver can accept (%s). Please upload in smaller chunks." |
232 | msgstr "" | 261 | msgstr "" |
233 | "a été importé avec succès en %d secondes : %d liens importés, %d liens " | 262 | "Le fichier que vous essayer d'envoyer est probablement plus lourd que ce que " |
234 | "écrasés, %d liens ignorés." | 263 | "le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " |
264 | "légères." | ||
235 | 265 | ||
236 | #: application/plugin/exception/PluginFileNotFoundException.php:21 | 266 | #: application/front/controller/admin/ManageShaareController.php:29 |
267 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
268 | msgid "Shaare a new link" | ||
269 | msgstr "Partager un nouveau lien" | ||
270 | |||
271 | #: application/front/controller/admin/ManageShaareController.php:78 | ||
272 | msgid "Note: " | ||
273 | msgstr "Note : " | ||
274 | |||
275 | #: application/front/controller/admin/ManageShaareController.php:109 | ||
276 | #: application/front/controller/admin/ManageShaareController.php:206 | ||
277 | #: application/front/controller/admin/ManageShaareController.php:275 | ||
278 | #: application/front/controller/admin/ManageShaareController.php:315 | ||
237 | #, php-format | 279 | #, php-format |
238 | msgid "Plugin \"%s\" files not found." | 280 | msgid "Bookmark with identifier %s could not be found." |
239 | msgstr "Les fichiers de l'extension \"%s\" sont introuvables." | 281 | msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." |
240 | 282 | ||
241 | #: application/render/PageBuilder.php:209 | 283 | #: application/front/controller/admin/ManageShaareController.php:194 |
242 | msgid "The page you are trying to reach does not exist or has been deleted." | 284 | #: application/front/controller/admin/ManageShaareController.php:252 |
243 | msgstr "La page que vous essayez de consulter n'existe pas ou a été supprimée." | 285 | msgid "Invalid bookmark ID provided." |
286 | msgstr "ID du lien non valide." | ||
244 | 287 | ||
245 | #: application/render/PageBuilder.php:211 | 288 | #: application/front/controller/admin/ManageShaareController.php:260 |
246 | msgid "404 Not Found" | 289 | msgid "Invalid visibility provided." |
247 | msgstr "404 Introuvable" | 290 | msgstr "Visibilité du lien non valide." |
248 | 291 | ||
249 | #: application/updater/Updater.php:99 | 292 | #: application/front/controller/admin/ManageShaareController.php:363 |
250 | #, fuzzy | 293 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 |
251 | #| msgid "Couldn't retrieve Updater class methods." | 294 | msgid "Edit" |
252 | msgid "Couldn't retrieve updater class methods." | 295 | msgstr "Modifier" |
253 | msgstr "Impossible de récupérer les méthodes de la classe Updater." | ||
254 | 296 | ||
255 | #: application/updater/Updater.php:526 index.php:1034 | 297 | #: application/front/controller/admin/ManageShaareController.php:366 |
256 | msgid "" | 298 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 |
257 | "You have enabled or changed thumbnails mode. <a href=\"?do=thumbs_update" | 299 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 |
258 | "\">Please synchronize them</a>." | 300 | msgid "Shaare" |
259 | msgstr "" | 301 | msgstr "Shaare" |
260 | "Vous avez activé ou changé le mode de miniatures. <a href=\"?do=thumbs_update" | 302 | |
261 | "\">Merci de les synchroniser</a>." | 303 | #: application/front/controller/admin/ManageTagController.php:29 |
304 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
305 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
306 | msgid "Manage tags" | ||
307 | msgstr "Gérer les tags" | ||
308 | |||
309 | #: application/front/controller/admin/ManageTagController.php:48 | ||
310 | msgid "Invalid tags provided." | ||
311 | msgstr "Les tags fournis ne sont pas valides." | ||
312 | |||
313 | #: application/front/controller/admin/ManageTagController.php:72 | ||
314 | #, php-format | ||
315 | msgid "The tag was removed from %d bookmark." | ||
316 | msgid_plural "The tag was removed from %d bookmarks." | ||
317 | msgstr[0] "Le tag a été supprimé du %d lien." | ||
318 | msgstr[1] "Le tag a été supprimé de %d liens." | ||
319 | |||
320 | #: application/front/controller/admin/ManageTagController.php:77 | ||
321 | #, php-format | ||
322 | msgid "The tag was renamed in %d bookmark." | ||
323 | msgid_plural "The tag was renamed in %d bookmarks." | ||
324 | msgstr[0] "Le tag a été renommé dans %d lien." | ||
325 | msgstr[1] "Le tag a été renommé dans %d liens." | ||
262 | 326 | ||
263 | #: application/updater/UpdaterUtils.php:32 | 327 | #: application/front/controller/admin/PasswordController.php:28 |
264 | msgid "Updates file path is not set, can't write updates." | 328 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 |
329 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
330 | msgid "Change password" | ||
331 | msgstr "Modifier le mot de passe" | ||
332 | |||
333 | #: application/front/controller/admin/PasswordController.php:55 | ||
334 | msgid "You must provide the current and new password to change it." | ||
265 | msgstr "" | 335 | msgstr "" |
266 | "Le chemin vers le fichier de mise à jour n'est pas défini, impossible " | 336 | "Vous devez fournir les mots de passe actuel et nouveau pour pouvoir le " |
267 | "d'écrire les mises à jour." | 337 | "modifier." |
338 | |||
339 | #: application/front/controller/admin/PasswordController.php:71 | ||
340 | msgid "The old password is not correct." | ||
341 | msgstr "L'ancien mot de passe est incorrect." | ||
268 | 342 | ||
269 | #: application/updater/UpdaterUtils.php:37 | 343 | #: application/front/controller/admin/PasswordController.php:97 |
270 | msgid "Unable to write updates in " | 344 | msgid "Your password has been changed" |
271 | msgstr "Impossible d'écrire les mises à jour dans " | 345 | msgstr "Votre mot de passe a été modifié" |
272 | 346 | ||
273 | #: application/updater/exception/UpdaterException.php:51 | 347 | #: application/front/controller/admin/PluginsController.php:45 |
274 | msgid "An error occurred while running the update " | 348 | msgid "Plugin Administration" |
275 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " | 349 | msgstr "Administration des plugins" |
276 | 350 | ||
277 | #: index.php:145 | 351 | #: application/front/controller/admin/PluginsController.php:75 |
278 | msgid "Shared links on " | 352 | msgid "Setting successfully saved." |
279 | msgstr "Liens partagés sur " | 353 | msgstr "Les paramètres ont été sauvegardés avec succès." |
280 | 354 | ||
281 | #: index.php:167 | 355 | #: application/front/controller/admin/PluginsController.php:78 |
282 | msgid "Insufficient permissions:" | 356 | msgid "Error while saving plugin configuration: " |
283 | msgstr "Permissions insuffisantes :" | 357 | msgstr "" |
358 | "Une erreur s'est produite lors de la sauvegarde de la configuration des " | ||
359 | "plugins : " | ||
284 | 360 | ||
285 | #: index.php:203 | 361 | #: application/front/controller/admin/ThumbnailsController.php:37 |
286 | msgid "I said: NO. You are banned for the moment. Go away." | 362 | #: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 |
287 | msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard." | 363 | msgid "Thumbnails update" |
364 | msgstr "Mise à jour des miniatures" | ||
288 | 365 | ||
289 | #: index.php:275 | 366 | #: application/front/controller/admin/ToolsController.php:31 |
290 | msgid "Wrong login/password." | 367 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 |
291 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." | 368 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:33 |
369 | msgid "Tools" | ||
370 | msgstr "Outils" | ||
371 | |||
372 | #: application/front/controller/visitor/BookmarkListController.php:115 | ||
373 | msgid "Search: " | ||
374 | msgstr "Recherche : " | ||
292 | 375 | ||
293 | #: index.php:398 index.php:404 | 376 | #: application/front/controller/visitor/DailyController.php:45 |
294 | msgid "Today" | 377 | msgid "Today" |
295 | msgstr "Aujourd'hui" | 378 | msgstr "Aujourd'hui" |
296 | 379 | ||
297 | #: index.php:400 | 380 | #: application/front/controller/visitor/DailyController.php:47 |
298 | msgid "Yesterday" | 381 | msgid "Yesterday" |
299 | msgstr "Hier" | 382 | msgstr "Hier" |
300 | 383 | ||
301 | #: index.php:484 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | 384 | #: application/front/controller/visitor/DailyController.php:85 |
302 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:46 | 385 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 |
386 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48 | ||
303 | msgid "Daily" | 387 | msgid "Daily" |
304 | msgstr "Quotidien" | 388 | msgstr "Quotidien" |
305 | 389 | ||
306 | #: index.php:593 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | 390 | #: application/front/controller/visitor/ErrorController.php:36 |
307 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | 391 | msgid "An unexpected error occurred." |
308 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 | 392 | msgstr "Une erreur inattendue s'est produite." |
309 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 | 393 | |
310 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:75 | 394 | #: application/front/controller/visitor/InstallController.php:73 |
311 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:99 | 395 | #, php-format |
396 | msgid "" | ||
397 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | ||
398 | "variable \"session.save_path\" is set correctly in your PHP config, and that " | ||
399 | "you have write access to it.<br>It currently points to %s.<br>On some " | ||
400 | "browsers, accessing your server via a hostname like 'localhost' or any " | ||
401 | "custom hostname without a dot causes cookie storage to fail. We recommend " | ||
402 | "accessing your server via it's IP address or Fully Qualified Domain Name.<br>" | ||
403 | msgstr "" | ||
404 | "<pre>Les sesssions ne semblent pas fonctionner sur ce serveur.<br>Assurez " | ||
405 | "vous que la variable « session.save_path » est correctement définie dans " | ||
406 | "votre fichier de configuration PHP, et que vous avez les droits d'écriture " | ||
407 | "dessus.<br>Ce paramètre pointe actuellement sur %s.<br>Sur certains " | ||
408 | "navigateurs, accéder à votre serveur depuis un nom d'hôte comme « localhost " | ||
409 | "» ou autre nom personnalisé sans point '.' entraine l'échec de la sauvegarde " | ||
410 | "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " | ||
411 | "adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" | ||
412 | |||
413 | #: application/front/controller/visitor/InstallController.php:144 | ||
414 | msgid "" | ||
415 | "Shaarli is now configured. Please login and start shaaring your bookmarks!" | ||
416 | msgstr "" | ||
417 | "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " | ||
418 | "shaare vos liens !" | ||
419 | |||
420 | #: application/front/controller/visitor/InstallController.php:158 | ||
421 | msgid "Insufficient permissions:" | ||
422 | msgstr "Permissions insuffisantes :" | ||
423 | |||
424 | #: application/front/controller/visitor/LoginController.php:46 | ||
425 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
426 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
427 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
428 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101 | ||
429 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:77 | ||
430 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:101 | ||
312 | msgid "Login" | 431 | msgid "Login" |
313 | msgstr "Connexion" | 432 | msgstr "Connexion" |
314 | 433 | ||
315 | #: index.php:608 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | 434 | #: application/front/controller/visitor/LoginController.php:78 |
316 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:41 | 435 | msgid "Wrong login/password." |
436 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." | ||
437 | |||
438 | #: application/front/controller/visitor/PictureWallController.php:29 | ||
439 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
440 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:43 | ||
317 | msgid "Picture wall" | 441 | msgid "Picture wall" |
318 | msgstr "Mur d'images" | 442 | msgstr "Mur d'images" |
319 | 443 | ||
320 | #: index.php:683 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 444 | #: application/front/controller/visitor/TagCloudController.php:80 |
321 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36 | 445 | #, fuzzy |
322 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | 446 | #| msgid "Tag list" |
323 | msgid "Tag cloud" | 447 | 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" | 448 | msgstr "Liste des tags" |
329 | 449 | ||
330 | #: index.php:944 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | 450 | #: application/front/exceptions/AlreadyInstalledException.php:11 |
331 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31 | 451 | msgid "Shaarli has already been installed. Login to edit the configuration." |
332 | msgid "Tools" | 452 | msgstr "" |
333 | msgstr "Outils" | 453 | "Shaarli est déjà installé. Connectez-vous pour modifier la configuration." |
334 | 454 | ||
335 | #: index.php:952 | 455 | #: application/front/exceptions/LoginBannedException.php:11 |
456 | msgid "" | ||
457 | "You have been banned after too many failed login attempts. Try again later." | ||
458 | msgstr "" | ||
459 | "Vous avez été banni après trop d'échecs d'authentification. Merci de " | ||
460 | "réessayer plus tard." | ||
461 | |||
462 | #: application/front/exceptions/OpenShaarliPasswordException.php:16 | ||
336 | msgid "You are not supposed to change a password on an Open Shaarli." | 463 | msgid "You are not supposed to change a password on an Open Shaarli." |
337 | msgstr "" | 464 | msgstr "" |
338 | "Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." | 465 | "Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." |
339 | 466 | ||
340 | #: index.php:957 index.php:1007 index.php:1094 index.php:1124 index.php:1234 | 467 | #: application/front/exceptions/ThumbnailsDisabledException.php:11 |
341 | #: index.php:1281 | 468 | msgid "Picture wall unavailable (thumbnails are disabled)." |
469 | msgstr "" | ||
470 | "Le mur d'images n'est pas disponible (les miniatures sont désactivées)." | ||
471 | |||
472 | #: application/front/exceptions/WrongTokenException.php:16 | ||
342 | msgid "Wrong token." | 473 | msgid "Wrong token." |
343 | msgstr "Jeton invalide." | 474 | msgstr "Jeton invalide." |
344 | 475 | ||
345 | #: index.php:966 | 476 | #: application/legacy/LegacyLinkDB.php:131 |
346 | msgid "The old password is not correct." | 477 | msgid "You are not authorized to add a link." |
347 | msgstr "L'ancien mot de passe est incorrect." | 478 | msgstr "Vous n'êtes pas autorisé à ajouter un lien." |
348 | 479 | ||
349 | #: index.php:993 | 480 | #: application/legacy/LegacyLinkDB.php:134 |
350 | msgid "Your password has been changed" | 481 | msgid "Internal Error: A link should always have an id and URL." |
351 | msgstr "Votre mot de passe a été modifié" | 482 | msgstr "Erreur interne : un lien devrait toujours avoir un ID et une URL." |
352 | 483 | ||
353 | #: index.php:997 | 484 | #: application/legacy/LegacyLinkDB.php:137 |
354 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 485 | msgid "You must specify an integer as a key." |
355 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | 486 | msgstr "Vous devez utiliser un entier comme clé." |
356 | msgid "Change password" | ||
357 | msgstr "Modifier le mot de passe" | ||
358 | 487 | ||
359 | #: index.php:1054 | 488 | #: application/legacy/LegacyLinkDB.php:140 |
360 | msgid "Configuration was saved." | 489 | msgid "Array offset and link ID must be equal." |
361 | msgstr "La configuration a été sauvegardée." | 490 | msgstr "La clé du tableau et l'ID du lien doivent être identiques." |
362 | 491 | ||
363 | #: index.php:1078 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | 492 | #: application/legacy/LegacyUpdater.php:104 |
364 | msgid "Configure" | 493 | msgid "Couldn't retrieve updater class methods." |
365 | msgstr "Configurer" | 494 | msgstr "Impossible de récupérer les méthodes de la classe Updater." |
366 | 495 | ||
367 | #: index.php:1088 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 496 | #: application/legacy/LegacyUpdater.php:538 |
368 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 497 | msgid "<a href=\"./admin/thumbnails\">" |
369 | msgid "Manage tags" | 498 | msgstr "<a href=\"./admin/thumbnails\">" |
370 | msgstr "Gérer les tags" | ||
371 | 499 | ||
372 | #: index.php:1107 | 500 | #: application/netscape/NetscapeBookmarkUtils.php:63 |
373 | #, php-format | 501 | msgid "Invalid export selection:" |
374 | msgid "The tag was removed from %d link." | 502 | msgstr "Sélection d'export invalide :" |
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 | 503 | ||
379 | #: index.php:1108 | 504 | #: application/netscape/NetscapeBookmarkUtils.php:215 |
380 | #, php-format | 505 | #, php-format |
381 | msgid "The tag was renamed in %d link." | 506 | msgid "File %s (%d bytes) " |
382 | msgid_plural "The tag was renamed in %d links." | 507 | msgstr "Le fichier %s (%d octets) " |
383 | msgstr[0] "Le tag a été renommé dans %d lien." | ||
384 | msgstr[1] "Le tag a été renommé dans %d liens." | ||
385 | |||
386 | #: index.php:1115 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
387 | msgid "Shaare a new link" | ||
388 | msgstr "Partager un nouveau lien" | ||
389 | |||
390 | #: index.php:1344 tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
391 | msgid "Edit" | ||
392 | msgstr "Modifier" | ||
393 | |||
394 | #: index.php:1344 index.php:1416 | ||
395 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
396 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26 | ||
397 | msgid "Shaare" | ||
398 | msgstr "Shaare" | ||
399 | |||
400 | #: index.php:1385 | ||
401 | msgid "Note: " | ||
402 | msgstr "Note : " | ||
403 | |||
404 | #: index.php:1424 | ||
405 | msgid "Invalid link ID provided" | ||
406 | msgstr "ID du lien non valide" | ||
407 | |||
408 | #: index.php:1444 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 | ||
409 | msgid "Export" | ||
410 | msgstr "Exporter" | ||
411 | 508 | ||
412 | #: index.php:1506 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 | 509 | #: application/netscape/NetscapeBookmarkUtils.php:217 |
413 | msgid "Import" | 510 | msgid "has an unknown file format. Nothing was imported." |
414 | msgstr "Importer" | 511 | msgstr "a un format inconnu. Rien n'a été importé." |
415 | 512 | ||
416 | #: index.php:1516 | 513 | #: application/netscape/NetscapeBookmarkUtils.php:221 |
417 | #, php-format | 514 | #, php-format |
418 | msgid "" | 515 | msgid "" |
419 | "The file you are trying to upload is probably bigger than what this " | 516 | "was successfully processed in %d seconds: %d bookmarks imported, %d " |
420 | "webserver can accept (%s). Please upload in smaller chunks." | 517 | "bookmarks overwritten, %d bookmarks skipped." |
421 | msgstr "" | 518 | msgstr "" |
422 | "Le fichier que vous essayer d'envoyer est probablement plus lourd que ce que " | 519 | "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 " | 520 | "écrasés, %d liens ignorés." |
424 | "légères." | ||
425 | |||
426 | #: index.php:1561 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
427 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
428 | msgid "Plugin administration" | ||
429 | msgstr "Administration des plugins" | ||
430 | 521 | ||
431 | #: index.php:1616 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 522 | #: application/plugin/PluginManager.php:122 |
432 | msgid "Thumbnails update" | 523 | msgid " [plugin incompatibility]: " |
433 | msgstr "Mise à jour des miniatures" | 524 | msgstr " [incompatibilité de l'extension] : " |
434 | 525 | ||
435 | #: index.php:1782 | 526 | #: application/plugin/exception/PluginFileNotFoundException.php:21 |
436 | msgid "Search: " | 527 | #, php-format |
437 | msgstr "Recherche : " | 528 | msgid "Plugin \"%s\" files not found." |
529 | msgstr "Les fichiers de l'extension \"%s\" sont introuvables." | ||
438 | 530 | ||
439 | #: index.php:1825 | 531 | #: application/render/PageCacheManager.php:32 |
440 | #, php-format | 532 | #, php-format |
441 | msgid "" | 533 | msgid "Cannot purge %s: no directory" |
442 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | 534 | 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 " | ||
444 | "you have write access to it.<br>It currently points to %s.<br>On some " | ||
445 | "browsers, accessing your server via a hostname like 'localhost' or any " | ||
446 | "custom hostname without a dot causes cookie storage to fail. We recommend " | ||
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 | 535 | ||
458 | #: index.php:1835 | 536 | #: application/updater/exception/UpdaterException.php:51 |
459 | msgid "Click to try again." | 537 | msgid "An error occurred while running the update " |
460 | msgstr "Cliquer ici pour réessayer." | 538 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " |
539 | |||
540 | #: index.php:62 | ||
541 | msgid "Shared bookmarks on " | ||
542 | msgstr "Liens partagés sur " | ||
461 | 543 | ||
462 | #: plugins/addlink_toolbar/addlink_toolbar.php:31 | 544 | #: plugins/addlink_toolbar/addlink_toolbar.php:31 |
463 | msgid "URI" | 545 | msgid "URI" |
@@ -472,11 +554,11 @@ msgstr "Shaare" | |||
472 | msgid "Adds the addlink input on the linklist page." | 554 | msgid "Adds the addlink input on the linklist page." |
473 | msgstr "Ajoute le formulaire d'ajout de liens sur la page principale." | 555 | msgstr "Ajoute le formulaire d'ajout de liens sur la page principale." |
474 | 556 | ||
475 | #: plugins/archiveorg/archiveorg.php:25 | 557 | #: plugins/archiveorg/archiveorg.php:26 |
476 | msgid "View on archive.org" | 558 | msgid "View on archive.org" |
477 | msgstr "Voir sur archive.org" | 559 | msgstr "Voir sur archive.org" |
478 | 560 | ||
479 | #: plugins/archiveorg/archiveorg.php:38 | 561 | #: plugins/archiveorg/archiveorg.php:39 |
480 | msgid "For each link, add an Archive.org icon." | 562 | msgid "For each link, add an Archive.org icon." |
481 | msgstr "Pour chaque lien, ajoute une icône pour Archive.org." | 563 | msgstr "Pour chaque lien, ajoute une icône pour Archive.org." |
482 | 564 | ||
@@ -506,7 +588,7 @@ msgstr "Couleur de fond (gris léger)" | |||
506 | msgid "Dark main color (e.g. visited links)" | 588 | msgid "Dark main color (e.g. visited links)" |
507 | msgstr "Couleur principale sombre (ex : les liens visités)" | 589 | msgstr "Couleur principale sombre (ex : les liens visités)" |
508 | 590 | ||
509 | #: plugins/demo_plugin/demo_plugin.php:482 | 591 | #: plugins/demo_plugin/demo_plugin.php:477 |
510 | msgid "" | 592 | msgid "" |
511 | "A demo plugin covering all use cases for template designers and plugin " | 593 | "A demo plugin covering all use cases for template designers and plugin " |
512 | "developers." | 594 | "developers." |
@@ -514,11 +596,11 @@ msgstr "" | |||
514 | "Une extension de démonstration couvrant tous les cas d'utilisation pour les " | 596 | "Une extension de démonstration couvrant tous les cas d'utilisation pour les " |
515 | "designers de thèmes et les développeurs d'extensions." | 597 | "designers de thèmes et les développeurs d'extensions." |
516 | 598 | ||
517 | #: plugins/demo_plugin/demo_plugin.php:483 | 599 | #: plugins/demo_plugin/demo_plugin.php:478 |
518 | msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed." | 600 | 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é." | 601 | msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé." |
520 | 602 | ||
521 | #: plugins/demo_plugin/demo_plugin.php:484 | 603 | #: plugins/demo_plugin/demo_plugin.php:479 |
522 | msgid "Other demo parameter" | 604 | msgid "Other demo parameter" |
523 | msgstr "Un autre paramètre de démo" | 605 | msgstr "Un autre paramètre de démo" |
524 | 606 | ||
@@ -540,36 +622,6 @@ msgstr "" | |||
540 | msgid "Isso server URL (without 'http://')" | 622 | msgid "Isso server URL (without 'http://')" |
541 | msgstr "URL du serveur Isso (sans 'http://')" | 623 | msgstr "URL du serveur Isso (sans 'http://')" |
542 | 624 | ||
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 | 625 | #: plugins/piwik/piwik.php:23 |
574 | msgid "" | 626 | msgid "" |
575 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " | 627 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " |
@@ -626,7 +678,7 @@ msgstr "Mauvaise réponse du hub %s" | |||
626 | msgid "Enable PubSubHubbub feed publishing." | 678 | msgid "Enable PubSubHubbub feed publishing." |
627 | msgstr "Active la publication de flux vers PubSubHubbub." | 679 | msgstr "Active la publication de flux vers PubSubHubbub." |
628 | 680 | ||
629 | #: plugins/qrcode/qrcode.php:72 plugins/wallabag/wallabag.php:68 | 681 | #: plugins/qrcode/qrcode.php:73 plugins/wallabag/wallabag.php:70 |
630 | msgid "For each link, add a QRCode icon." | 682 | msgid "For each link, add a QRCode icon." |
631 | msgstr "Pour chaque lien, ajouter une icône de QRCode." | 683 | msgstr "Pour chaque lien, ajouter une icône de QRCode." |
632 | 684 | ||
@@ -642,24 +694,14 @@ msgstr "" | |||
642 | msgid "Save to wallabag" | 694 | msgid "Save to wallabag" |
643 | msgstr "Sauvegarder dans Wallabag" | 695 | msgstr "Sauvegarder dans Wallabag" |
644 | 696 | ||
645 | #: plugins/wallabag/wallabag.php:69 | 697 | #: plugins/wallabag/wallabag.php:71 |
646 | msgid "Wallabag API URL" | 698 | msgid "Wallabag API URL" |
647 | msgstr "URL de l'API Wallabag" | 699 | msgstr "URL de l'API Wallabag" |
648 | 700 | ||
649 | #: plugins/wallabag/wallabag.php:70 | 701 | #: plugins/wallabag/wallabag.php:72 |
650 | msgid "Wallabag API version (1 or 2)" | 702 | msgid "Wallabag API version (1 or 2)" |
651 | msgstr "Version de l'API Wallabag (1 ou 2)" | 703 | msgstr "Version de l'API Wallabag (1 ou 2)" |
652 | 704 | ||
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 | 705 | #: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 |
664 | msgid "Sorry, nothing to see here." | 706 | msgid "Sorry, nothing to see here." |
665 | msgstr "Désolé, il y a rien à voir ici." | 707 | msgstr "Désolé, il y a rien à voir ici." |
@@ -698,10 +740,11 @@ msgid "Rename" | |||
698 | msgstr "Renommer" | 740 | msgstr "Renommer" |
699 | 741 | ||
700 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | 742 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 |
701 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | 743 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:93 |
702 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 | 744 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 |
703 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145 | 745 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 |
704 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:145 | 746 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 |
747 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67 | ||
705 | msgid "Delete" | 748 | msgid "Delete" |
706 | msgstr "Supprimer" | 749 | msgstr "Supprimer" |
707 | 750 | ||
@@ -713,33 +756,6 @@ msgstr "Vous pouvez aussi modifier les tags dans la" | |||
713 | msgid "tag list" | 756 | msgid "tag list" |
714 | msgstr "liste des tags" | 757 | msgstr "liste des tags" |
715 | 758 | ||
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 | 759 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
744 | msgid "title" | 760 | msgid "title" |
745 | msgstr "titre" | 761 | msgstr "titre" |
@@ -756,109 +772,132 @@ msgstr "Valeur par défaut" | |||
756 | msgid "Theme" | 772 | msgid "Theme" |
757 | msgstr "Thème" | 773 | msgstr "Thème" |
758 | 774 | ||
759 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 | 775 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85 |
760 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78 | 776 | msgid "Description formatter" |
777 | msgstr "Format des descriptions" | ||
778 | |||
779 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114 | ||
780 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
761 | msgid "Language" | 781 | msgid "Language" |
762 | msgstr "Langue" | 782 | msgstr "Langue" |
763 | 783 | ||
764 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116 | 784 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143 |
765 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | 785 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101 |
766 | msgid "Timezone" | 786 | msgid "Timezone" |
767 | msgstr "Fuseau horaire" | 787 | msgstr "Fuseau horaire" |
768 | 788 | ||
769 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | 789 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 |
770 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | 790 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
771 | msgid "Continent" | 791 | msgid "Continent" |
772 | msgstr "Continent" | 792 | msgstr "Continent" |
773 | 793 | ||
774 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | 794 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 |
775 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | 795 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
776 | msgid "City" | 796 | msgid "City" |
777 | msgstr "Ville" | 797 | msgstr "Ville" |
778 | 798 | ||
779 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164 | 799 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:191 |
780 | msgid "Disable session cookie hijacking protection" | 800 | msgid "Disable session cookie hijacking protection" |
781 | msgstr "Désactiver la protection contre le détournement de cookies" | 801 | msgstr "Désactiver la protection contre le détournement de cookies" |
782 | 802 | ||
783 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166 | 803 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:193 |
784 | msgid "Check this if you get disconnected or if your IP address changes often" | 804 | msgid "Check this if you get disconnected or if your IP address changes often" |
785 | msgstr "" | 805 | msgstr "" |
786 | "Cocher cette case si vous êtes souvent déconnecté ou si votre adresse IP " | 806 | "Cocher cette case si vous êtes souvent déconnecté ou si votre adresse IP " |
787 | "change souvent" | 807 | "change souvent" |
788 | 808 | ||
789 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 | 809 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:210 |
790 | msgid "Private links by default" | 810 | msgid "Private links by default" |
791 | msgstr "Liens privés par défaut" | 811 | msgstr "Liens privés par défaut" |
792 | 812 | ||
793 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184 | 813 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:211 |
794 | msgid "All new links are private by default" | 814 | msgid "All new links are private by default" |
795 | msgstr "Tous les nouveaux liens sont privés par défaut" | 815 | msgstr "Tous les nouveaux liens sont privés par défaut" |
796 | 816 | ||
797 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | 817 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:226 |
798 | msgid "RSS direct links" | 818 | msgid "RSS direct links" |
799 | msgstr "Liens directs dans le flux RSS" | 819 | msgstr "Liens directs dans le flux RSS" |
800 | 820 | ||
801 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200 | 821 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:227 |
802 | msgid "Check this to use direct URL instead of permalink in feeds" | 822 | msgid "Check this to use direct URL instead of permalink in feeds" |
803 | msgstr "" | 823 | msgstr "" |
804 | "Cocher cette case pour utiliser des liens directs au lieu des permaliens " | 824 | "Cocher cette case pour utiliser des liens directs au lieu des permaliens " |
805 | "dans le flux RSS" | 825 | "dans le flux RSS" |
806 | 826 | ||
807 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215 | 827 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:242 |
808 | msgid "Hide public links" | 828 | msgid "Hide public links" |
809 | msgstr "Cacher les liens publics" | 829 | msgstr "Cacher les liens publics" |
810 | 830 | ||
811 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216 | 831 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:243 |
812 | msgid "Do not show any links if the user is not logged in" | 832 | msgid "Do not show any links if the user is not logged in" |
813 | msgstr "N'afficher aucun lien sans être connecté" | 833 | msgstr "N'afficher aucun lien sans être connecté" |
814 | 834 | ||
815 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231 | 835 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:258 |
816 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | 836 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:149 |
817 | msgid "Check updates" | 837 | msgid "Check updates" |
818 | msgstr "Vérifier les mises à jour" | 838 | msgstr "Vérifier les mises à jour" |
819 | 839 | ||
820 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232 | 840 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:259 |
821 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152 | 841 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151 |
822 | msgid "Notify me when a new release is ready" | 842 | msgid "Notify me when a new release is ready" |
823 | msgstr "Me notifier lorsqu'une nouvelle version est disponible" | 843 | msgstr "Me notifier lorsqu'une nouvelle version est disponible" |
824 | 844 | ||
825 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247 | 845 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274 |
826 | msgid "Automatically retrieve description for new bookmarks" | 846 | msgid "Automatically retrieve description for new bookmarks" |
827 | msgstr "Récupérer automatiquement la description" | 847 | msgstr "Récupérer automatiquement la description" |
828 | 848 | ||
829 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248 | 849 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:275 |
830 | msgid "Shaarli will try to retrieve the description from meta HTML headers" | 850 | msgid "Shaarli will try to retrieve the description from meta HTML headers" |
831 | msgstr "" | 851 | msgstr "" |
832 | "Shaarli essaiera de récupérer la description depuis les balises HTML meta " | 852 | "Shaarli essaiera de récupérer la description depuis les balises HTML meta " |
833 | "dans les entêtes" | 853 | "dans les entêtes" |
834 | 854 | ||
835 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263 | 855 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:290 |
836 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | 856 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168 |
837 | msgid "Enable REST API" | 857 | msgid "Enable REST API" |
838 | msgstr "Activer l'API REST" | 858 | msgstr "Activer l'API REST" |
839 | 859 | ||
840 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:264 | 860 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:291 |
841 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170 | 861 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 |
842 | msgid "Allow third party software to use Shaarli such as mobile application" | 862 | msgid "Allow third party software to use Shaarli such as mobile application" |
843 | msgstr "" | 863 | msgstr "" |
844 | "Permet aux applications tierces d'utiliser Shaarli, par exemple les " | 864 | "Permet aux applications tierces d'utiliser Shaarli, par exemple les " |
845 | "applications mobiles" | 865 | "applications mobiles" |
846 | 866 | ||
847 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:279 | 867 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:306 |
848 | msgid "API secret" | 868 | msgid "API secret" |
849 | msgstr "Clé d'API secrète" | 869 | msgstr "Clé d'API secrète" |
850 | 870 | ||
851 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:293 | 871 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320 |
852 | msgid "Enable thumbnails" | 872 | msgid "Enable thumbnails" |
853 | msgstr "Activer les miniatures" | 873 | msgstr "Activer les miniatures" |
854 | 874 | ||
855 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:301 | 875 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:324 |
876 | msgid "You need to enable the extension <code>php-gd</code> to use thumbnails." | ||
877 | msgstr "" | ||
878 | "Vous devez activer l'extension <code>php-gd</code> pour utiliser les " | ||
879 | "miniatures." | ||
880 | |||
881 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328 | ||
856 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 | 882 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 |
857 | msgid "Synchronize thumbnails" | 883 | msgid "Synchronize thumbnails" |
858 | msgstr "Synchroniser les miniatures" | 884 | msgstr "Synchroniser les miniatures" |
859 | 885 | ||
860 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328 | 886 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339 |
861 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | 887 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 |
888 | msgid "All" | ||
889 | msgstr "Tous" | ||
890 | |||
891 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343 | ||
892 | msgid "Only common media hosts" | ||
893 | msgstr "Seulement les hébergeurs de média connus" | ||
894 | |||
895 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347 | ||
896 | msgid "None" | ||
897 | msgstr "Aucune" | ||
898 | |||
899 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355 | ||
900 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | ||
862 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | 901 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 |
863 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | 902 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 |
864 | msgid "Save" | 903 | msgid "Save" |
@@ -884,27 +923,27 @@ msgstr "Tous les liens d'un jour sur une page." | |||
884 | msgid "Next day" | 923 | msgid "Next day" |
885 | msgstr "Jour suivant" | 924 | msgstr "Jour suivant" |
886 | 925 | ||
887 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 926 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 |
888 | msgid "Edit Shaare" | 927 | msgid "Edit Shaare" |
889 | msgstr "Modifier le Shaare" | 928 | msgstr "Modifier le Shaare" |
890 | 929 | ||
891 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 930 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 |
892 | msgid "New Shaare" | 931 | msgid "New Shaare" |
893 | msgstr "Nouveau Shaare" | 932 | msgstr "Nouveau Shaare" |
894 | 933 | ||
895 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | 934 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 |
896 | msgid "Created:" | 935 | msgid "Created:" |
897 | msgstr "Création :" | 936 | msgstr "Création :" |
898 | 937 | ||
899 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | 938 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
900 | msgid "URL" | 939 | msgid "URL" |
901 | msgstr "URL" | 940 | msgstr "URL" |
902 | 941 | ||
903 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 | 942 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 |
904 | msgid "Title" | 943 | msgid "Title" |
905 | msgstr "Titre" | 944 | msgstr "Titre" |
906 | 945 | ||
907 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 | 946 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 |
908 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | 947 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 |
909 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 | 948 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 |
910 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 | 949 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 |
@@ -912,17 +951,29 @@ msgstr "Titre" | |||
912 | msgid "Description" | 951 | msgid "Description" |
913 | msgstr "Description" | 952 | msgstr "Description" |
914 | 953 | ||
915 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | 954 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 |
916 | msgid "Tags" | 955 | msgid "Tags" |
917 | msgstr "Tags" | 956 | msgstr "Tags" |
918 | 957 | ||
919 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57 | 958 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 |
920 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 959 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 |
921 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167 | 960 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 |
922 | msgid "Private" | 961 | msgid "Private" |
923 | msgstr "Privé" | 962 | msgstr "Privé" |
924 | 963 | ||
925 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | 964 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 |
965 | msgid "Description will be rendered with" | ||
966 | msgstr "La description sera générée avec" | ||
967 | |||
968 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 | ||
969 | msgid "Markdown syntax documentation" | ||
970 | msgstr "Documentation sur la syntaxe Markdown" | ||
971 | |||
972 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | ||
973 | msgid "Markdown syntax" | ||
974 | msgstr "la syntaxe Markdown" | ||
975 | |||
976 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | ||
926 | msgid "Apply Changes" | 977 | msgid "Apply Changes" |
927 | msgstr "Appliquer les changements" | 978 | msgstr "Appliquer les changements" |
928 | 979 | ||
@@ -930,19 +981,19 @@ msgstr "Appliquer les changements" | |||
930 | msgid "Export Database" | 981 | msgid "Export Database" |
931 | msgstr "Exporter les données" | 982 | msgstr "Exporter les données" |
932 | 983 | ||
933 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | 984 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 |
934 | msgid "Selection" | 985 | msgid "Selection" |
935 | msgstr "Choisir" | 986 | msgstr "Choisir" |
936 | 987 | ||
937 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | 988 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 |
938 | msgid "Public" | 989 | msgid "Public" |
939 | msgstr "Publics" | 990 | msgstr "Publics" |
940 | 991 | ||
941 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52 | 992 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51 |
942 | msgid "Prepend note permalinks with this Shaarli instance's URL" | 993 | 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" | 994 | msgstr "Préfixer les liens de note avec l'URL de l'instance de Shaarli" |
944 | 995 | ||
945 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53 | 996 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52 |
946 | msgid "Useful to import bookmarks in a web browser" | 997 | msgid "Useful to import bookmarks in a web browser" |
947 | msgstr "Utile pour importer les marques-pages dans un navigateur" | 998 | msgstr "Utile pour importer les marques-pages dans un navigateur" |
948 | 999 | ||
@@ -993,29 +1044,29 @@ msgstr "" | |||
993 | "Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci de " | 1044 | "Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci de " |
994 | "le configurer." | 1045 | "le configurer." |
995 | 1046 | ||
996 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 | 1047 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 |
997 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 | 1048 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 |
998 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165 | 1049 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167 |
999 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:165 | 1050 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:167 |
1000 | msgid "Username" | 1051 | msgid "Username" |
1001 | msgstr "Nom d'utilisateur" | 1052 | msgstr "Nom d'utilisateur" |
1002 | 1053 | ||
1003 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | 1054 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 |
1004 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | 1055 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20 |
1005 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166 | 1056 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168 |
1006 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:166 | 1057 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:168 |
1007 | msgid "Password" | 1058 | msgid "Password" |
1008 | msgstr "Mot de passe" | 1059 | msgstr "Mot de passe" |
1009 | 1060 | ||
1010 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63 | 1061 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:62 |
1011 | msgid "Shaarli title" | 1062 | msgid "Shaarli title" |
1012 | msgstr "Titre du Shaarli" | 1063 | msgstr "Titre du Shaarli" |
1013 | 1064 | ||
1014 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | 1065 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 |
1015 | msgid "My links" | 1066 | msgid "My links" |
1016 | msgstr "Mes liens" | 1067 | msgstr "Mes liens" |
1017 | 1068 | ||
1018 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182 | 1069 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181 |
1019 | msgid "Install" | 1070 | msgid "Install" |
1020 | msgstr "Installer" | 1071 | msgstr "Installer" |
1021 | 1072 | ||
@@ -1034,21 +1085,31 @@ msgstr[0] "lien privé" | |||
1034 | msgstr[1] "liens privés" | 1085 | msgstr[1] "liens privés" |
1035 | 1086 | ||
1036 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 | 1087 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 |
1037 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121 | 1088 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 |
1038 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:121 | 1089 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:123 |
1039 | msgid "Search text" | 1090 | msgid "Search text" |
1040 | msgstr "Recherche texte" | 1091 | msgstr "Recherche texte" |
1041 | 1092 | ||
1042 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 | 1093 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 |
1043 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:128 | 1094 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130 |
1044 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:128 | 1095 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:130 |
1045 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1096 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 |
1046 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64 | 1097 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 |
1047 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1098 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 |
1048 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | 1099 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 |
1049 | msgid "Filter by tag" | 1100 | msgid "Filter by tag" |
1050 | msgstr "Filtrer par tag" | 1101 | msgstr "Filtrer par tag" |
1051 | 1102 | ||
1103 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
1104 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 | ||
1105 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | ||
1106 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:87 | ||
1107 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:139 | ||
1108 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
1109 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45 | ||
1110 | msgid "Search" | ||
1111 | msgstr "Rechercher" | ||
1112 | |||
1052 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 | 1113 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 |
1053 | msgid "Nothing found." | 1114 | msgid "Nothing found." |
1054 | msgstr "Aucun résultat." | 1115 | msgstr "Aucun résultat." |
@@ -1069,40 +1130,41 @@ msgid "tagged" | |||
1069 | msgstr "taggé" | 1130 | msgstr "taggé" |
1070 | 1131 | ||
1071 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:133 | 1132 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:133 |
1133 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
1072 | msgid "Remove tag" | 1134 | msgid "Remove tag" |
1073 | msgstr "Retirer le tag" | 1135 | msgstr "Retirer le tag" |
1074 | 1136 | ||
1075 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:142 | 1137 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 |
1076 | msgid "with status" | 1138 | msgid "with status" |
1077 | msgstr "avec le statut" | 1139 | msgstr "avec le statut" |
1078 | 1140 | ||
1079 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153 | 1141 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155 |
1080 | msgid "without any tag" | 1142 | msgid "without any tag" |
1081 | msgstr "sans tag" | 1143 | msgstr "sans tag" |
1082 | 1144 | ||
1083 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 | 1145 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175 |
1084 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | 1146 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 |
1085 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43 | 1147 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43 |
1086 | msgid "Fold" | 1148 | msgid "Fold" |
1087 | msgstr "Replier" | 1149 | msgstr "Replier" |
1088 | 1150 | ||
1089 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175 | 1151 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177 |
1090 | msgid "Edited: " | 1152 | msgid "Edited: " |
1091 | msgstr "Modifié : " | 1153 | msgstr "Modifié : " |
1092 | 1154 | ||
1093 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 | 1155 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181 |
1094 | msgid "permalink" | 1156 | msgid "permalink" |
1095 | msgstr "permalien" | 1157 | msgstr "permalien" |
1096 | 1158 | ||
1097 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181 | 1159 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 |
1098 | msgid "Add tag" | 1160 | msgid "Add tag" |
1099 | msgstr "Ajouter un tag" | 1161 | msgstr "Ajouter un tag" |
1100 | 1162 | ||
1101 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 | 1163 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185 |
1102 | msgid "Toggle sticky" | 1164 | msgid "Toggle sticky" |
1103 | msgstr "Changer statut épinglé" | 1165 | msgstr "Changer statut épinglé" |
1104 | 1166 | ||
1105 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185 | 1167 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187 |
1106 | msgid "Sticky" | 1168 | msgid "Sticky" |
1107 | msgstr "Épinglé" | 1169 | msgstr "Épinglé" |
1108 | 1170 | ||
@@ -1145,16 +1207,9 @@ msgstr "Replier tout" | |||
1145 | msgid "Links per page" | 1207 | msgid "Links per page" |
1146 | msgstr "Liens par page" | 1208 | msgstr "Liens par page" |
1147 | 1209 | ||
1148 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | 1210 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 |
1149 | msgid "" | 1211 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 |
1150 | "You have been banned after too many failed login attempts. Try again later." | 1212 | #: 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" | 1213 | msgid "Remember me" |
1159 | msgstr "Rester connecté" | 1214 | msgstr "Rester connecté" |
1160 | 1215 | ||
@@ -1185,62 +1240,64 @@ msgstr "Déplier tout" | |||
1185 | msgid "Are you sure you want to delete this link?" | 1240 | msgid "Are you sure you want to delete this link?" |
1186 | msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?" | 1241 | msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?" |
1187 | 1242 | ||
1188 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 | 1243 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:11 |
1189 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:90 | 1244 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:11 |
1190 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:65 | 1245 | msgid "Menu" |
1191 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:90 | 1246 | msgstr "Menu" |
1247 | |||
1248 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 | ||
1249 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:38 | ||
1250 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1251 | msgid "Tag cloud" | ||
1252 | msgstr "Nuage de tags" | ||
1253 | |||
1254 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67 | ||
1255 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92 | ||
1256 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:67 | ||
1257 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:92 | ||
1192 | msgid "RSS Feed" | 1258 | msgid "RSS Feed" |
1193 | msgstr "Flux RSS" | 1259 | msgstr "Flux RSS" |
1194 | 1260 | ||
1195 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70 | 1261 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 |
1196 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 | 1262 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108 |
1197 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:70 | 1263 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:72 |
1198 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:106 | 1264 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:108 |
1199 | msgid "Logout" | 1265 | msgid "Logout" |
1200 | msgstr "Déconnexion" | 1266 | msgstr "Déconnexion" |
1201 | 1267 | ||
1202 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | 1268 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152 |
1203 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:150 | 1269 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:152 |
1204 | msgid "Set public" | 1270 | msgid "Set public" |
1205 | msgstr "Rendre public" | 1271 | msgstr "Rendre public" |
1206 | 1272 | ||
1207 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155 | 1273 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:157 |
1208 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155 | 1274 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:157 |
1209 | msgid "Set private" | 1275 | msgid "Set private" |
1210 | msgstr "Rendre privé" | 1276 | msgstr "Rendre privé" |
1211 | 1277 | ||
1212 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187 | 1278 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189 |
1213 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:187 | 1279 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:189 |
1214 | msgid "is available" | 1280 | msgid "is available" |
1215 | msgstr "est disponible" | 1281 | msgstr "est disponible" |
1216 | 1282 | ||
1217 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:194 | 1283 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:196 |
1218 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:194 | 1284 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:196 |
1219 | msgid "Error" | 1285 | msgid "Error" |
1220 | msgstr "Erreur" | 1286 | msgstr "Erreur" |
1221 | 1287 | ||
1222 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 1288 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 |
1223 | msgid "Picture wall unavailable (thumbnails are disabled)." | 1289 | msgid "There is no cached thumbnail." |
1224 | msgstr "" | 1290 | 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 | 1291 | ||
1227 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | 1292 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 |
1228 | #, fuzzy | 1293 | msgid "Try to synchronize them." |
1229 | #| msgid "" | 1294 | 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 | 1295 | ||
1239 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1296 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 |
1240 | msgid "Picture Wall" | 1297 | msgid "Picture Wall" |
1241 | msgstr "Mur d'images" | 1298 | msgstr "Mur d'images" |
1242 | 1299 | ||
1243 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1300 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 |
1244 | msgid "pics" | 1301 | msgid "pics" |
1245 | msgstr "images" | 1302 | msgstr "images" |
1246 | 1303 | ||
@@ -1249,6 +1306,11 @@ msgid "You need to enable Javascript to change plugin loading order." | |||
1249 | msgstr "" | 1306 | msgstr "" |
1250 | "Vous devez activer Javascript pour pouvoir modifier l'ordre des extensions." | 1307 | "Vous devez activer Javascript pour pouvoir modifier l'ordre des extensions." |
1251 | 1308 | ||
1309 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
1310 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
1311 | msgid "Plugin administration" | ||
1312 | msgstr "Administration des plugins" | ||
1313 | |||
1252 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | 1314 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
1253 | msgid "Enabled Plugins" | 1315 | msgid "Enabled Plugins" |
1254 | msgstr "Extensions activées" | 1316 | msgstr "Extensions activées" |
@@ -1314,6 +1376,14 @@ msgstr "tags" | |||
1314 | msgid "List all links with those tags" | 1376 | msgid "List all links with those tags" |
1315 | msgstr "Lister tous les liens avec ces tags" | 1377 | msgstr "Lister tous les liens avec ces tags" |
1316 | 1378 | ||
1379 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1380 | msgid "Tag list" | ||
1381 | msgstr "Liste des tags" | ||
1382 | |||
1383 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 | ||
1384 | msgid "Rename tag" | ||
1385 | msgstr "Renommer le tag" | ||
1386 | |||
1317 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3 | 1387 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3 |
1318 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3 | 1388 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3 |
1319 | msgid "Sort by:" | 1389 | msgid "Sort by:" |
@@ -1457,6 +1527,68 @@ msgstr "" | |||
1457 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " | 1527 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " |
1458 | "Ajouter aux favoris »" | 1528 | "Ajouter aux favoris »" |
1459 | 1529 | ||
1530 | #, fuzzy | ||
1531 | #~| msgid "Selection" | ||
1532 | #~ msgid ".ui-selecting" | ||
1533 | #~ msgstr "Choisir" | ||
1534 | |||
1535 | #, fuzzy | ||
1536 | #~| msgid "Documentation" | ||
1537 | #~ msgid "document" | ||
1538 | #~ msgstr "Documentation" | ||
1539 | |||
1540 | #~ msgid "The page you are trying to reach does not exist or has been deleted." | ||
1541 | #~ msgstr "" | ||
1542 | #~ "La page que vous essayez de consulter n'existe pas ou a été supprimée." | ||
1543 | |||
1544 | #~ msgid "404 Not Found" | ||
1545 | #~ msgstr "404 Introuvable" | ||
1546 | |||
1547 | #~ msgid "Updates file path is not set, can't write updates." | ||
1548 | #~ msgstr "" | ||
1549 | #~ "Le chemin vers le fichier de mise à jour n'est pas défini, impossible " | ||
1550 | #~ "d'écrire les mises à jour." | ||
1551 | |||
1552 | #~ msgid "Unable to write updates in " | ||
1553 | #~ msgstr "Impossible d'écrire les mises à jour dans " | ||
1554 | |||
1555 | #~ msgid "I said: NO. You are banned for the moment. Go away." | ||
1556 | #~ msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard." | ||
1557 | |||
1558 | #~ msgid "Click to try again." | ||
1559 | #~ msgstr "Cliquer ici pour réessayer." | ||
1560 | |||
1561 | #~ msgid "" | ||
1562 | #~ "Render shaare description with Markdown syntax.<br><strong>Warning</" | ||
1563 | #~ "strong>:\n" | ||
1564 | #~ "If your shaared descriptions contained HTML tags before enabling the " | ||
1565 | #~ "markdown plugin,\n" | ||
1566 | #~ "enabling it might break your page.\n" | ||
1567 | #~ "See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
1568 | #~ "markdown#html-rendering\">README</a>." | ||
1569 | #~ msgstr "" | ||
1570 | #~ "Utilise la syntaxe Markdown pour la description des liens." | ||
1571 | #~ "<br><strong>Attention</strong> :\n" | ||
1572 | #~ "Si vous aviez des descriptions contenant du HTML avant d'activer cette " | ||
1573 | #~ "extension,\n" | ||
1574 | #~ "l'activer pourrait déformer vos pages.\n" | ||
1575 | #~ "Voir le <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
1576 | #~ "markdown#html-rendering\">README</a>." | ||
1577 | |||
1578 | #~ msgid "Synchonize thumbnails" | ||
1579 | #~ msgstr "Synchroniser les miniatures" | ||
1580 | |||
1581 | #, fuzzy | ||
1582 | #~| msgid "" | ||
1583 | #~| "You don't have any cached thumbnail. Try to <a href=\"?do=thumbs_update" | ||
1584 | #~| "\">synchronize them</a>." | ||
1585 | #~ msgid "" | ||
1586 | #~ "There is no cached thumbnail. Try to <a href=\"?do=thumbs_update" | ||
1587 | #~ "\">synchronize them</a>." | ||
1588 | #~ msgstr "" | ||
1589 | #~ "Il n'y a aucune miniature en cache. Essayer de <a href=\"?do=thumbs_update" | ||
1590 | #~ "\">les synchroniser</a>." | ||
1591 | |||
1460 | #~ msgid "" | 1592 | #~ msgid "" |
1461 | #~ "You need to browse your Shaarli over <strong>HTTPS</strong> to use this " | 1593 | #~ "You need to browse your Shaarli over <strong>HTTPS</strong> to use this " |
1462 | #~ "functionality." | 1594 | #~ "functionality." |
@@ -12,120 +12,27 @@ | |||
12 | * Licence: http://www.opensource.org/licenses/zlib-license.php | 12 | * Licence: http://www.opensource.org/licenses/zlib-license.php |
13 | */ | 13 | */ |
14 | 14 | ||
15 | // Set 'UTC' as the default timezone if it is not defined in php.ini | ||
16 | // See http://php.net/manual/en/datetime.configuration.php#ini.date.timezone | ||
17 | if (date_default_timezone_get() == '') { | ||
18 | date_default_timezone_set('UTC'); | ||
19 | } | ||
20 | |||
21 | /* | ||
22 | * PHP configuration | ||
23 | */ | ||
24 | |||
25 | // http://server.com/x/shaarli --> /shaarli/ | ||
26 | define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); | ||
27 | |||
28 | // High execution time in case of problematic imports/exports. | ||
29 | ini_set('max_input_time', '60'); | ||
30 | |||
31 | // Try to set max upload file size and read | ||
32 | ini_set('memory_limit', '128M'); | ||
33 | ini_set('post_max_size', '16M'); | ||
34 | ini_set('upload_max_filesize', '16M'); | ||
35 | |||
36 | // See all error except warnings | ||
37 | error_reporting(E_ALL^E_WARNING); | ||
38 | |||
39 | // 3rd-party libraries | ||
40 | if (! file_exists(__DIR__ . '/vendor/autoload.php')) { | ||
41 | header('Content-Type: text/plain; charset=utf-8'); | ||
42 | echo "Error: missing Composer configuration\n\n" | ||
43 | ."If you installed Shaarli through Git or using the development branch,\n" | ||
44 | ."please refer to the installation documentation to install PHP" | ||
45 | ." dependencies using Composer:\n" | ||
46 | ."- https://shaarli.readthedocs.io/en/master/Server-configuration/\n" | ||
47 | ."- https://shaarli.readthedocs.io/en/master/Download-and-Installation/"; | ||
48 | exit; | ||
49 | } | ||
50 | require_once 'inc/rain.tpl.class.php'; | 15 | require_once 'inc/rain.tpl.class.php'; |
51 | require_once __DIR__ . '/vendor/autoload.php'; | 16 | require_once __DIR__ . '/vendor/autoload.php'; |
52 | 17 | ||
53 | // Shaarli library | 18 | // Shaarli library |
54 | require_once 'application/bookmark/LinkUtils.php'; | 19 | require_once 'application/bookmark/LinkUtils.php'; |
55 | require_once 'application/config/ConfigPlugin.php'; | 20 | require_once 'application/config/ConfigPlugin.php'; |
56 | require_once 'application/feed/Cache.php'; | ||
57 | require_once 'application/http/HttpUtils.php'; | 21 | require_once 'application/http/HttpUtils.php'; |
58 | require_once 'application/http/UrlUtils.php'; | 22 | require_once 'application/http/UrlUtils.php'; |
59 | require_once 'application/updater/UpdaterUtils.php'; | ||
60 | require_once 'application/FileUtils.php'; | ||
61 | require_once 'application/TimeZone.php'; | 23 | require_once 'application/TimeZone.php'; |
62 | require_once 'application/Utils.php'; | 24 | require_once 'application/Utils.php'; |
63 | 25 | ||
64 | use Shaarli\ApplicationUtils; | 26 | require_once __DIR__ . '/init.php'; |
65 | use Shaarli\Bookmark\Bookmark; | 27 | |
66 | use Shaarli\Bookmark\BookmarkFileService; | ||
67 | use Shaarli\Bookmark\BookmarkFilter; | ||
68 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
69 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
70 | use Shaarli\Config\ConfigManager; | 28 | use Shaarli\Config\ConfigManager; |
71 | use Shaarli\Container\ContainerBuilder; | 29 | use Shaarli\Container\ContainerBuilder; |
72 | use Shaarli\Feed\CachedPage; | ||
73 | use Shaarli\Feed\FeedBuilder; | ||
74 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
75 | use Shaarli\Formatter\FormatterFactory; | ||
76 | use Shaarli\History; | ||
77 | use Shaarli\Languages; | 30 | use Shaarli\Languages; |
78 | use Shaarli\Netscape\NetscapeBookmarkUtils; | 31 | use Shaarli\Security\CookieManager; |
79 | use Shaarli\Plugin\PluginManager; | ||
80 | use Shaarli\Render\PageBuilder; | ||
81 | use Shaarli\Render\ThemeUtils; | ||
82 | use Shaarli\Router; | ||
83 | use Shaarli\Security\LoginManager; | 32 | use Shaarli\Security\LoginManager; |
84 | use Shaarli\Security\SessionManager; | 33 | use Shaarli\Security\SessionManager; |
85 | use Shaarli\Thumbnailer; | ||
86 | use Shaarli\Updater\Updater; | ||
87 | use Shaarli\Updater\UpdaterUtils; | ||
88 | use Slim\App; | 34 | use Slim\App; |
89 | 35 | ||
90 | // Ensure the PHP version is supported | ||
91 | try { | ||
92 | ApplicationUtils::checkPHPVersion('7.1', PHP_VERSION); | ||
93 | } catch (Exception $exc) { | ||
94 | header('Content-Type: text/plain; charset=utf-8'); | ||
95 | echo $exc->getMessage(); | ||
96 | exit; | ||
97 | } | ||
98 | |||
99 | define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE)); | ||
100 | |||
101 | // Force cookie path (but do not change lifetime) | ||
102 | $cookie = session_get_cookie_params(); | ||
103 | $cookiedir = ''; | ||
104 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { | ||
105 | $cookiedir = dirname($_SERVER["SCRIPT_NAME"]).'/'; | ||
106 | } | ||
107 | // Set default cookie expiration and path. | ||
108 | session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']); | ||
109 | // Set session parameters on server side. | ||
110 | // Use cookies to store session. | ||
111 | ini_set('session.use_cookies', 1); | ||
112 | // Force cookies for session (phpsessionID forbidden in URL). | ||
113 | ini_set('session.use_only_cookies', 1); | ||
114 | // Prevent PHP form using sessionID in URL if cookies are disabled. | ||
115 | ini_set('session.use_trans_sid', false); | ||
116 | |||
117 | session_name('shaarli'); | ||
118 | // Start session if needed (Some server auto-start sessions). | ||
119 | if (session_status() == PHP_SESSION_NONE) { | ||
120 | session_start(); | ||
121 | } | ||
122 | |||
123 | // Regenerate session ID if invalid or not defined in cookie. | ||
124 | if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli'])) { | ||
125 | session_regenerate_id(true); | ||
126 | $_COOKIE['shaarli'] = session_id(); | ||
127 | } | ||
128 | |||
129 | $conf = new ConfigManager(); | 36 | $conf = new ConfigManager(); |
130 | 37 | ||
131 | // In dev mode, throw exception on any warning | 38 | // In dev mode, throw exception on any warning |
@@ -133,20 +40,16 @@ if ($conf->get('dev.debug', false)) { | |||
133 | // See all errors (for debugging only) | 40 | // See all errors (for debugging only) |
134 | error_reporting(-1); | 41 | error_reporting(-1); |
135 | 42 | ||
136 | set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) { | 43 | set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext) { |
137 | throw new ErrorException($errstr, 0, $errno, $errfile, $errline); | 44 | throw new ErrorException($errstr, 0, $errno, $errfile, $errline); |
138 | }); | 45 | }); |
139 | } | 46 | } |
140 | 47 | ||
141 | $sessionManager = new SessionManager($_SESSION, $conf); | 48 | $sessionManager = new SessionManager($_SESSION, $conf, session_save_path()); |
142 | $loginManager = new LoginManager($conf, $sessionManager); | 49 | $sessionManager->initialize(); |
50 | $cookieManager = new CookieManager($_COOKIE); | ||
51 | $loginManager = new LoginManager($conf, $sessionManager, $cookieManager); | ||
143 | $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); | 52 | $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); |
144 | $clientIpId = client_ip_id($_SERVER); | ||
145 | |||
146 | // LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead. | ||
147 | if (! defined('LC_MESSAGES')) { | ||
148 | define('LC_MESSAGES', LC_COLLATE); | ||
149 | } | ||
150 | 53 | ||
151 | // Sniff browser language and set date format accordingly. | 54 | // Sniff browser language and set date format accordingly. |
152 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { | 55 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { |
@@ -157,1773 +60,76 @@ new Languages(setlocale(LC_MESSAGES, 0), $conf); | |||
157 | 60 | ||
158 | $conf->setEmpty('general.timezone', date_default_timezone_get()); | 61 | $conf->setEmpty('general.timezone', date_default_timezone_get()); |
159 | $conf->setEmpty('general.title', t('Shared bookmarks on '). escape(index_url($_SERVER))); | 62 | $conf->setEmpty('general.title', t('Shared bookmarks on '). escape(index_url($_SERVER))); |
63 | |||
160 | RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory | 64 | RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory |
161 | RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory | 65 | RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory |
162 | 66 | ||
163 | $pluginManager = new PluginManager($conf); | ||
164 | $pluginManager->load($conf->get('general.enabled_plugins')); | ||
165 | |||
166 | date_default_timezone_set($conf->get('general.timezone', 'UTC')); | 67 | date_default_timezone_set($conf->get('general.timezone', 'UTC')); |
167 | 68 | ||
168 | ob_start(); // Output buffering for the page cache. | 69 | $loginManager->checkLoginState(client_ip_id($_SERVER)); |
169 | |||
170 | // Prevent caching on client side or proxy: (yes, it's ugly) | ||
171 | header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); | ||
172 | header("Cache-Control: no-store, no-cache, must-revalidate"); | ||
173 | header("Cache-Control: post-check=0, pre-check=0", false); | ||
174 | header("Pragma: no-cache"); | ||
175 | |||
176 | if (! is_file($conf->getConfigFileExt())) { | ||
177 | // Ensure Shaarli has proper access to its resources | ||
178 | $errors = ApplicationUtils::checkResourcePermissions($conf); | ||
179 | |||
180 | if ($errors != array()) { | ||
181 | $message = '<p>'. t('Insufficient permissions:') .'</p><ul>'; | ||
182 | |||
183 | foreach ($errors as $error) { | ||
184 | $message .= '<li>'.$error.'</li>'; | ||
185 | } | ||
186 | $message .= '</ul>'; | ||
187 | |||
188 | header('Content-Type: text/html; charset=utf-8'); | ||
189 | echo $message; | ||
190 | exit; | ||
191 | } | ||
192 | |||
193 | // Display the installation form if no existing config is found | ||
194 | install($conf, $sessionManager, $loginManager); | ||
195 | } | ||
196 | |||
197 | $loginManager->checkLoginState($_COOKIE, $clientIpId); | ||
198 | |||
199 | /** | ||
200 | * Adapter function to ensure compatibility with third-party templates | ||
201 | * | ||
202 | * @see https://github.com/shaarli/Shaarli/pull/1086 | ||
203 | * | ||
204 | * @return bool true when the user is logged in, false otherwise | ||
205 | */ | ||
206 | function isLoggedIn() | ||
207 | { | ||
208 | global $loginManager; | ||
209 | return $loginManager->isLoggedIn(); | ||
210 | } | ||
211 | |||
212 | |||
213 | // ------------------------------------------------------------------------------------------ | ||
214 | // Process login form: Check if login/password is correct. | ||
215 | if (isset($_POST['login'])) { | ||
216 | if (! $loginManager->canLogin($_SERVER)) { | ||
217 | die(t('I said: NO. You are banned for the moment. Go away.')); | ||
218 | } | ||
219 | if (isset($_POST['password']) | ||
220 | && $sessionManager->checkToken($_POST['token']) | ||
221 | && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password']) | ||
222 | ) { | ||
223 | $loginManager->handleSuccessfulLogin($_SERVER); | ||
224 | |||
225 | $cookiedir = ''; | ||
226 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { | ||
227 | // Note: Never forget the trailing slash on the cookie path! | ||
228 | $cookiedir = dirname($_SERVER["SCRIPT_NAME"]) . '/'; | ||
229 | } | ||
230 | |||
231 | if (!empty($_POST['longlastingsession'])) { | ||
232 | // Keep the session cookie even after the browser closes | ||
233 | $sessionManager->setStaySignedIn(true); | ||
234 | $expirationTime = $sessionManager->extendSession(); | ||
235 | |||
236 | setcookie( | ||
237 | $loginManager::$STAY_SIGNED_IN_COOKIE, | ||
238 | $loginManager->getStaySignedInToken(), | ||
239 | $expirationTime, | ||
240 | WEB_PATH | ||
241 | ); | ||
242 | } else { | ||
243 | // Standard session expiration (=when browser closes) | ||
244 | $expirationTime = 0; | ||
245 | } | ||
246 | |||
247 | // Send cookie with the new expiration date to the browser | ||
248 | session_destroy(); | ||
249 | session_set_cookie_params($expirationTime, $cookiedir, $_SERVER['SERVER_NAME']); | ||
250 | session_start(); | ||
251 | session_regenerate_id(true); | ||
252 | |||
253 | // Optional redirect after login: | ||
254 | if (isset($_GET['post'])) { | ||
255 | $uri = './?post='. urlencode($_GET['post']); | ||
256 | foreach (array('description', 'source', 'title', 'tags') as $param) { | ||
257 | if (!empty($_GET[$param])) { | ||
258 | $uri .= '&'.$param.'='.urlencode($_GET[$param]); | ||
259 | } | ||
260 | } | ||
261 | header('Location: '. $uri); | ||
262 | exit; | ||
263 | } | ||
264 | |||
265 | if (isset($_GET['edit_link'])) { | ||
266 | header('Location: ./?edit_link='. escape($_GET['edit_link'])); | ||
267 | exit; | ||
268 | } | ||
269 | |||
270 | if (isset($_POST['returnurl'])) { | ||
271 | // Prevent loops over login screen. | ||
272 | if (strpos($_POST['returnurl'], '/login') === false) { | ||
273 | header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST'])); | ||
274 | exit; | ||
275 | } | ||
276 | } | ||
277 | header('Location: ./?'); | ||
278 | exit; | ||
279 | } else { | ||
280 | $loginManager->handleFailedLogin($_SERVER); | ||
281 | $redir = '?username='. urlencode($_POST['login']); | ||
282 | if (isset($_GET['post'])) { | ||
283 | $redir .= '&post=' . urlencode($_GET['post']); | ||
284 | foreach (array('description', 'source', 'title', 'tags') as $param) { | ||
285 | if (!empty($_GET[$param])) { | ||
286 | $redir .= '&' . $param . '=' . urlencode($_GET[$param]); | ||
287 | } | ||
288 | } | ||
289 | } | ||
290 | // Redirect to login screen. | ||
291 | echo '<script>alert("'. t("Wrong login/password.") .'");document.location=\'./login'.$redir.'\';</script>'; | ||
292 | exit; | ||
293 | } | ||
294 | } | ||
295 | |||
296 | // ------------------------------------------------------------------------------------------ | ||
297 | // Token management for XSRF protection | ||
298 | // Token should be used in any form which acts on data (create,update,delete,import...). | ||
299 | if (!isset($_SESSION['tokens'])) { | ||
300 | $_SESSION['tokens']=array(); // Token are attached to the session. | ||
301 | } | ||
302 | |||
303 | /** | ||
304 | * Daily RSS feed: 1 RSS entry per day giving all the bookmarks on that day. | ||
305 | * Gives the last 7 days (which have bookmarks). | ||
306 | * This RSS feed cannot be filtered. | ||
307 | * | ||
308 | * @param BookmarkServiceInterface $bookmarkService | ||
309 | * @param ConfigManager $conf Configuration Manager instance | ||
310 | * @param LoginManager $loginManager LoginManager instance | ||
311 | */ | ||
312 | function showDailyRSS($bookmarkService, $conf, $loginManager) | ||
313 | { | ||
314 | // Cache system | ||
315 | $query = $_SERVER['QUERY_STRING']; | ||
316 | $cache = new CachedPage( | ||
317 | $conf->get('config.PAGE_CACHE'), | ||
318 | page_url($_SERVER), | ||
319 | startsWith($query, 'do=dailyrss') && !$loginManager->isLoggedIn() | ||
320 | ); | ||
321 | $cached = $cache->cachedVersion(); | ||
322 | if (!empty($cached)) { | ||
323 | echo $cached; | ||
324 | exit; | ||
325 | } | ||
326 | |||
327 | /* Some Shaarlies may have very few bookmarks, so we need to look | ||
328 | back in time until we have enough days ($nb_of_days). | ||
329 | */ | ||
330 | $nb_of_days = 7; // We take 7 days. | ||
331 | $today = date('Ymd'); | ||
332 | $days = array(); | ||
333 | |||
334 | foreach ($bookmarkService->search() as $bookmark) { | ||
335 | $day = $bookmark->getCreated()->format('Ymd'); // Extract day (without time) | ||
336 | if (strcmp($day, $today) < 0) { | ||
337 | if (empty($days[$day])) { | ||
338 | $days[$day] = array(); | ||
339 | } | ||
340 | $days[$day][] = $bookmark; | ||
341 | } | ||
342 | |||
343 | if (count($days) > $nb_of_days) { | ||
344 | break; // Have we collected enough days? | ||
345 | } | ||
346 | } | ||
347 | |||
348 | // Build the RSS feed. | ||
349 | header('Content-Type: application/rss+xml; charset=utf-8'); | ||
350 | $pageaddr = escape(index_url($_SERVER)); | ||
351 | echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">'; | ||
352 | echo '<channel>'; | ||
353 | echo '<title>Daily - '. $conf->get('general.title') . '</title>'; | ||
354 | echo '<link>'. $pageaddr .'</link>'; | ||
355 | echo '<description>Daily shared bookmarks</description>'; | ||
356 | echo '<language>en-en</language>'; | ||
357 | echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL; | ||
358 | |||
359 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
360 | $formatter = $factory->getFormatter(); | ||
361 | $formatter->addContextData('index_url', index_url($_SERVER)); | ||
362 | // For each day. | ||
363 | /** @var Bookmark[] $bookmarks */ | ||
364 | foreach ($days as $day => $bookmarks) { | ||
365 | $formattedBookmarks = []; | ||
366 | $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | ||
367 | $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. | ||
368 | |||
369 | // We pre-format some fields for proper output. | ||
370 | foreach ($bookmarks as $key => $bookmark) { | ||
371 | $formattedBookmarks[$key] = $formatter->format($bookmark); | ||
372 | // This page is a bit specific, we need raw description to calculate the length | ||
373 | $formattedBookmarks[$key]['formatedDescription'] = $formattedBookmarks[$key]['description']; | ||
374 | $formattedBookmarks[$key]['description'] = $bookmark->getDescription(); | ||
375 | |||
376 | if ($bookmark->isNote()) { | ||
377 | $link['url'] = index_url($_SERVER) . $bookmark->getUrl(); // make permalink URL absolute | ||
378 | } | ||
379 | } | ||
380 | |||
381 | // Then build the HTML for this day: | ||
382 | $tpl = new RainTPL(); | ||
383 | $tpl->assign('title', $conf->get('general.title')); | ||
384 | $tpl->assign('daydate', $dayDate->getTimestamp()); | ||
385 | $tpl->assign('absurl', $absurl); | ||
386 | $tpl->assign('links', $formattedBookmarks); | ||
387 | $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS))); | ||
388 | $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false)); | ||
389 | $tpl->assign('index_url', $pageaddr); | ||
390 | $html = $tpl->draw('dailyrss', true); | ||
391 | |||
392 | echo $html . PHP_EOL; | ||
393 | } | ||
394 | echo '</channel></rss><!-- Cached version of '. escape(page_url($_SERVER)) .' -->'; | ||
395 | |||
396 | $cache->cache(ob_get_contents()); | ||
397 | ob_end_flush(); | ||
398 | exit; | ||
399 | } | ||
400 | |||
401 | /** | ||
402 | * Show the 'Daily' page. | ||
403 | * | ||
404 | * @param PageBuilder $pageBuilder Template engine wrapper. | ||
405 | * @param BookmarkServiceInterface $bookmarkService instance. | ||
406 | * @param ConfigManager $conf Configuration Manager instance. | ||
407 | * @param PluginManager $pluginManager Plugin Manager instance. | ||
408 | * @param LoginManager $loginManager Login Manager instance | ||
409 | */ | ||
410 | function showDaily($pageBuilder, $bookmarkService, $conf, $pluginManager, $loginManager) | ||
411 | { | ||
412 | if (isset($_GET['day'])) { | ||
413 | $day = $_GET['day']; | ||
414 | if ($day === date('Ymd', strtotime('now'))) { | ||
415 | $pageBuilder->assign('dayDesc', t('Today')); | ||
416 | } elseif ($day === date('Ymd', strtotime('-1 days'))) { | ||
417 | $pageBuilder->assign('dayDesc', t('Yesterday')); | ||
418 | } | ||
419 | } else { | ||
420 | $day = date('Ymd', strtotime('now')); // Today, in format YYYYMMDD. | ||
421 | $pageBuilder->assign('dayDesc', t('Today')); | ||
422 | } | ||
423 | |||
424 | $days = $bookmarkService->days(); | ||
425 | $i = array_search($day, $days); | ||
426 | if ($i === false && count($days)) { | ||
427 | // no bookmarks for day, but at least one day with bookmarks | ||
428 | $i = count($days) - 1; | ||
429 | $day = $days[$i]; | ||
430 | } | ||
431 | $previousday = ''; | ||
432 | $nextday = ''; | ||
433 | |||
434 | if ($i !== false) { | ||
435 | if ($i >= 1) { | ||
436 | $previousday = $days[$i - 1]; | ||
437 | } | ||
438 | if ($i < count($days) - 1) { | ||
439 | $nextday = $days[$i + 1]; | ||
440 | } | ||
441 | } | ||
442 | try { | ||
443 | $linksToDisplay = $bookmarkService->filterDay($day); | ||
444 | } catch (Exception $exc) { | ||
445 | error_log($exc); | ||
446 | $linksToDisplay = []; | ||
447 | } | ||
448 | |||
449 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
450 | $formatter = $factory->getFormatter(); | ||
451 | // We pre-format some fields for proper output. | ||
452 | foreach ($linksToDisplay as $key => $bookmark) { | ||
453 | $linksToDisplay[$key] = $formatter->format($bookmark); | ||
454 | // This page is a bit specific, we need raw description to calculate the length | ||
455 | $linksToDisplay[$key]['formatedDescription'] = $linksToDisplay[$key]['description']; | ||
456 | $linksToDisplay[$key]['description'] = $bookmark->getDescription(); | ||
457 | } | ||
458 | |||
459 | $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | ||
460 | $data = array( | ||
461 | 'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false), | ||
462 | 'linksToDisplay' => $linksToDisplay, | ||
463 | 'day' => $dayDate->getTimestamp(), | ||
464 | 'dayDate' => $dayDate, | ||
465 | 'previousday' => $previousday, | ||
466 | 'nextday' => $nextday, | ||
467 | ); | ||
468 | |||
469 | /* Hook is called before column construction so that plugins don't have | ||
470 | to deal with columns. */ | ||
471 | $pluginManager->executeHooks('render_daily', $data, array('loggedin' => $loginManager->isLoggedIn())); | ||
472 | |||
473 | /* We need to spread the articles on 3 columns. | ||
474 | I did not want to use a JavaScript lib like http://masonry.desandro.com/ | ||
475 | so I manually spread entries with a simple method: I roughly evaluate the | ||
476 | height of a div according to title and description length. | ||
477 | */ | ||
478 | $columns = array(array(), array(), array()); // Entries to display, for each column. | ||
479 | $fill = array(0, 0, 0); // Rough estimate of columns fill. | ||
480 | foreach ($data['linksToDisplay'] as $key => $bookmark) { | ||
481 | // Roughly estimate length of entry (by counting characters) | ||
482 | // Title: 30 chars = 1 line. 1 line is 30 pixels height. | ||
483 | // Description: 836 characters gives roughly 342 pixel height. | ||
484 | // This is not perfect, but it's usually OK. | ||
485 | $length = strlen($bookmark['title']) + (342 * strlen($bookmark['description'])) / 836; | ||
486 | if (! empty($bookmark['thumbnail'])) { | ||
487 | $length += 100; // 1 thumbnails roughly takes 100 pixels height. | ||
488 | } | ||
489 | // Then put in column which is the less filled: | ||
490 | $smallest = min($fill); // find smallest value in array. | ||
491 | $index = array_search($smallest, $fill); // find index of this smallest value. | ||
492 | array_push($columns[$index], $bookmark); // Put entry in this column. | ||
493 | $fill[$index] += $length; | ||
494 | } | ||
495 | |||
496 | $data['cols'] = $columns; | ||
497 | |||
498 | foreach ($data as $key => $value) { | ||
499 | $pageBuilder->assign($key, $value); | ||
500 | } | ||
501 | |||
502 | $pageBuilder->assign('pagetitle', t('Daily') .' - '. $conf->get('general.title', 'Shaarli')); | ||
503 | $pageBuilder->renderPage('daily'); | ||
504 | exit; | ||
505 | } | ||
506 | |||
507 | /** | ||
508 | * Renders the linklist | ||
509 | * | ||
510 | * @param pageBuilder $PAGE pageBuilder instance. | ||
511 | * @param BookmarkServiceInterface $linkDb instance. | ||
512 | * @param ConfigManager $conf Configuration Manager instance. | ||
513 | * @param PluginManager $pluginManager Plugin Manager instance. | ||
514 | */ | ||
515 | function showLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager) | ||
516 | { | ||
517 | buildLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager); | ||
518 | $PAGE->renderPage('linklist'); | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * Render HTML page (according to URL parameters and user rights) | ||
523 | * | ||
524 | * @param ConfigManager $conf Configuration Manager instance. | ||
525 | * @param PluginManager $pluginManager Plugin Manager instance, | ||
526 | * @param BookmarkServiceInterface $bookmarkService | ||
527 | * @param History $history instance | ||
528 | * @param SessionManager $sessionManager SessionManager instance | ||
529 | * @param LoginManager $loginManager LoginManager instance | ||
530 | */ | ||
531 | function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionManager, $loginManager) | ||
532 | { | ||
533 | $updater = new Updater( | ||
534 | UpdaterUtils::read_updates_file($conf->get('resource.updates')), | ||
535 | $bookmarkService, | ||
536 | $conf, | ||
537 | $loginManager->isLoggedIn() | ||
538 | ); | ||
539 | try { | ||
540 | $newUpdates = $updater->update(); | ||
541 | if (! empty($newUpdates)) { | ||
542 | UpdaterUtils::write_updates_file( | ||
543 | $conf->get('resource.updates'), | ||
544 | $updater->getDoneUpdates() | ||
545 | ); | ||
546 | } | ||
547 | } catch (Exception $e) { | ||
548 | die($e->getMessage()); | ||
549 | } | ||
550 | |||
551 | $PAGE = new PageBuilder($conf, $_SESSION, $bookmarkService, $sessionManager->generateToken(), $loginManager->isLoggedIn()); | ||
552 | $PAGE->assign('linkcount', $bookmarkService->count(BookmarkFilter::$ALL)); | ||
553 | $PAGE->assign('privateLinkcount', $bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
554 | $PAGE->assign('plugin_errors', $pluginManager->getErrors()); | ||
555 | |||
556 | // Determine which page will be rendered. | ||
557 | $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; | ||
558 | $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn()); | ||
559 | |||
560 | if (// if the user isn't logged in | ||
561 | !$loginManager->isLoggedIn() && | ||
562 | // and Shaarli doesn't have public content... | ||
563 | $conf->get('privacy.hide_public_links') && | ||
564 | // and is configured to enforce the login | ||
565 | $conf->get('privacy.force_login') && | ||
566 | // and the current page isn't already the login page | ||
567 | $targetPage !== Router::$PAGE_LOGIN && | ||
568 | // and the user is not requesting a feed (which would lead to a different content-type as expected) | ||
569 | $targetPage !== Router::$PAGE_FEED_ATOM && | ||
570 | $targetPage !== Router::$PAGE_FEED_RSS | ||
571 | ) { | ||
572 | // force current page to be the login page | ||
573 | $targetPage = Router::$PAGE_LOGIN; | ||
574 | } | ||
575 | |||
576 | // Call plugin hooks for header, footer and includes, specifying which page will be rendered. | ||
577 | // Then assign generated data to RainTPL. | ||
578 | $common_hooks = array( | ||
579 | 'includes', | ||
580 | 'header', | ||
581 | 'footer', | ||
582 | ); | ||
583 | |||
584 | foreach ($common_hooks as $name) { | ||
585 | $plugin_data = array(); | ||
586 | $pluginManager->executeHooks( | ||
587 | 'render_' . $name, | ||
588 | $plugin_data, | ||
589 | array( | ||
590 | 'target' => $targetPage, | ||
591 | 'loggedin' => $loginManager->isLoggedIn() | ||
592 | ) | ||
593 | ); | ||
594 | $PAGE->assign('plugins_' . $name, $plugin_data); | ||
595 | } | ||
596 | |||
597 | // -------- Display login form. | ||
598 | if ($targetPage == Router::$PAGE_LOGIN) { | ||
599 | header('Location: ./login'); | ||
600 | exit; | ||
601 | } | ||
602 | // -------- User wants to logout. | ||
603 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) { | ||
604 | invalidateCaches($conf->get('resource.page_cache')); | ||
605 | $sessionManager->logout(); | ||
606 | setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH); | ||
607 | header('Location: ?'); | ||
608 | exit; | ||
609 | } | ||
610 | |||
611 | // -------- Picture wall | ||
612 | if ($targetPage == Router::$PAGE_PICWALL) { | ||
613 | $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli')); | ||
614 | if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { | ||
615 | $PAGE->assign('linksToDisplay', []); | ||
616 | $PAGE->renderPage('picwall'); | ||
617 | exit; | ||
618 | } | ||
619 | |||
620 | // Optionally filter the results: | ||
621 | $links = $bookmarkService->search($_GET); | ||
622 | $linksToDisplay = []; | ||
623 | |||
624 | // Get only bookmarks which have a thumbnail. | ||
625 | // Note: we do not retrieve thumbnails here, the request is too heavy. | ||
626 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
627 | $formatter = $factory->getFormatter(); | ||
628 | foreach ($links as $key => $link) { | ||
629 | if ($link->getThumbnail() !== false) { | ||
630 | $linksToDisplay[] = $formatter->format($link); | ||
631 | } | ||
632 | } | ||
633 | |||
634 | $data = [ | ||
635 | 'linksToDisplay' => $linksToDisplay, | ||
636 | ]; | ||
637 | $pluginManager->executeHooks('render_picwall', $data, ['loggedin' => $loginManager->isLoggedIn()]); | ||
638 | |||
639 | foreach ($data as $key => $value) { | ||
640 | $PAGE->assign($key, $value); | ||
641 | } | ||
642 | |||
643 | $PAGE->renderPage('picwall'); | ||
644 | exit; | ||
645 | } | ||
646 | |||
647 | // -------- Tag cloud | ||
648 | if ($targetPage == Router::$PAGE_TAGCLOUD) { | ||
649 | $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; | ||
650 | $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; | ||
651 | $tags = $bookmarkService->bookmarksCountPerTag($filteringTags, $visibility); | ||
652 | |||
653 | // We sort tags alphabetically, then choose a font size according to count. | ||
654 | // First, find max value. | ||
655 | $maxcount = 0; | ||
656 | foreach ($tags as $value) { | ||
657 | $maxcount = max($maxcount, $value); | ||
658 | } | ||
659 | |||
660 | alphabetical_sort($tags, false, true); | ||
661 | |||
662 | $logMaxCount = $maxcount > 1 ? log($maxcount, 30) : 1; | ||
663 | $tagList = array(); | ||
664 | foreach ($tags as $key => $value) { | ||
665 | if (in_array($key, $filteringTags)) { | ||
666 | continue; | ||
667 | } | ||
668 | // Tag font size scaling: | ||
669 | // default 15 and 30 logarithm bases affect scaling, | ||
670 | // 2.2 and 0.8 are arbitrary font sizes in em. | ||
671 | $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; | ||
672 | $tagList[$key] = array( | ||
673 | 'count' => $value, | ||
674 | 'size' => number_format($size, 2, '.', ''), | ||
675 | ); | ||
676 | } | ||
677 | |||
678 | $searchTags = implode(' ', escape($filteringTags)); | ||
679 | $data = array( | ||
680 | 'search_tags' => $searchTags, | ||
681 | 'tags' => $tagList, | ||
682 | ); | ||
683 | $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => $loginManager->isLoggedIn())); | ||
684 | |||
685 | foreach ($data as $key => $value) { | ||
686 | $PAGE->assign($key, $value); | ||
687 | } | ||
688 | |||
689 | $searchTags = ! empty($searchTags) ? $searchTags .' - ' : ''; | ||
690 | $PAGE->assign('pagetitle', $searchTags. t('Tag cloud') .' - '. $conf->get('general.title', 'Shaarli')); | ||
691 | $PAGE->renderPage('tag.cloud'); | ||
692 | exit; | ||
693 | } | ||
694 | |||
695 | // -------- Tag list | ||
696 | if ($targetPage == Router::$PAGE_TAGLIST) { | ||
697 | $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; | ||
698 | $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; | ||
699 | $tags = $bookmarkService->bookmarksCountPerTag($filteringTags, $visibility); | ||
700 | foreach ($filteringTags as $tag) { | ||
701 | if (array_key_exists($tag, $tags)) { | ||
702 | unset($tags[$tag]); | ||
703 | } | ||
704 | } | ||
705 | |||
706 | if (! empty($_GET['sort']) && $_GET['sort'] === 'alpha') { | ||
707 | alphabetical_sort($tags, false, true); | ||
708 | } | ||
709 | |||
710 | $searchTags = implode(' ', escape($filteringTags)); | ||
711 | $data = [ | ||
712 | 'search_tags' => $searchTags, | ||
713 | 'tags' => $tags, | ||
714 | ]; | ||
715 | $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => $loginManager->isLoggedIn()]); | ||
716 | |||
717 | foreach ($data as $key => $value) { | ||
718 | $PAGE->assign($key, $value); | ||
719 | } | ||
720 | |||
721 | $searchTags = ! empty($searchTags) ? $searchTags .' - ' : ''; | ||
722 | $PAGE->assign('pagetitle', $searchTags . t('Tag list') .' - '. $conf->get('general.title', 'Shaarli')); | ||
723 | $PAGE->renderPage('tag.list'); | ||
724 | exit; | ||
725 | } | ||
726 | |||
727 | // Daily page. | ||
728 | if ($targetPage == Router::$PAGE_DAILY) { | ||
729 | showDaily($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); | ||
730 | } | ||
731 | |||
732 | // ATOM and RSS feed. | ||
733 | if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) { | ||
734 | $feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; | ||
735 | header('Content-Type: application/'. $feedType .'+xml; charset=utf-8'); | ||
736 | |||
737 | // Cache system | ||
738 | $query = $_SERVER['QUERY_STRING']; | ||
739 | $cache = new CachedPage( | ||
740 | $conf->get('resource.page_cache'), | ||
741 | page_url($_SERVER), | ||
742 | startsWith($query, 'do='. $targetPage) && !$loginManager->isLoggedIn() | ||
743 | ); | ||
744 | $cached = $cache->cachedVersion(); | ||
745 | if (!empty($cached)) { | ||
746 | echo $cached; | ||
747 | exit; | ||
748 | } | ||
749 | |||
750 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
751 | // Generate data. | ||
752 | $feedGenerator = new FeedBuilder( | ||
753 | $bookmarkService, | ||
754 | $factory->getFormatter(), | ||
755 | $feedType, | ||
756 | $_SERVER, | ||
757 | $_GET, | ||
758 | $loginManager->isLoggedIn() | ||
759 | ); | ||
760 | $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); | ||
761 | $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn()); | ||
762 | $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); | ||
763 | $data = $feedGenerator->buildData(); | ||
764 | |||
765 | // Process plugin hook. | ||
766 | $pluginManager->executeHooks('render_feed', $data, array( | ||
767 | 'loggedin' => $loginManager->isLoggedIn(), | ||
768 | 'target' => $targetPage, | ||
769 | )); | ||
770 | |||
771 | // Render the template. | ||
772 | $PAGE->assignAll($data); | ||
773 | $PAGE->renderPage('feed.'. $feedType); | ||
774 | $cache->cache(ob_get_contents()); | ||
775 | ob_end_flush(); | ||
776 | exit; | ||
777 | } | ||
778 | |||
779 | // Display opensearch plugin (XML) | ||
780 | if ($targetPage == Router::$PAGE_OPENSEARCH) { | ||
781 | header('Content-Type: application/xml; charset=utf-8'); | ||
782 | $PAGE->assign('serverurl', index_url($_SERVER)); | ||
783 | $PAGE->renderPage('opensearch'); | ||
784 | exit; | ||
785 | } | ||
786 | |||
787 | // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...) | ||
788 | if (isset($_GET['addtag'])) { | ||
789 | // Get previous URL (http_referer) and add the tag to the searchtags parameters in query. | ||
790 | if (empty($_SERVER['HTTP_REFERER'])) { | ||
791 | // In case browser does not send HTTP_REFERER | ||
792 | header('Location: ?searchtags='.urlencode($_GET['addtag'])); | ||
793 | exit; | ||
794 | } | ||
795 | parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params); | ||
796 | |||
797 | // Prevent redirection loop | ||
798 | if (isset($params['addtag'])) { | ||
799 | unset($params['addtag']); | ||
800 | } | ||
801 | |||
802 | // Check if this tag is already in the search query and ignore it if it is. | ||
803 | // Each tag is always separated by a space | ||
804 | if (isset($params['searchtags'])) { | ||
805 | $current_tags = explode(' ', $params['searchtags']); | ||
806 | } else { | ||
807 | $current_tags = array(); | ||
808 | } | ||
809 | $addtag = true; | ||
810 | foreach ($current_tags as $value) { | ||
811 | if ($value === $_GET['addtag']) { | ||
812 | $addtag = false; | ||
813 | break; | ||
814 | } | ||
815 | } | ||
816 | // Append the tag if necessary | ||
817 | if (empty($params['searchtags'])) { | ||
818 | $params['searchtags'] = trim($_GET['addtag']); | ||
819 | } elseif ($addtag) { | ||
820 | $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']); | ||
821 | } | ||
822 | |||
823 | // We also remove page (keeping the same page has no sense, since the | ||
824 | // results are different) | ||
825 | unset($params['page']); | ||
826 | |||
827 | header('Location: ?'.http_build_query($params)); | ||
828 | exit; | ||
829 | } | ||
830 | |||
831 | // -------- User clicks on a tag in result count: Remove the tag from the list of searched tags (searchtags=...) | ||
832 | if (isset($_GET['removetag'])) { | ||
833 | // Get previous URL (http_referer) and remove the tag from the searchtags parameters in query. | ||
834 | if (empty($_SERVER['HTTP_REFERER'])) { | ||
835 | header('Location: ?'); | ||
836 | exit; | ||
837 | } | ||
838 | |||
839 | // In case browser does not send HTTP_REFERER | ||
840 | parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params); | ||
841 | 70 | ||
842 | // Prevent redirection loop | 71 | $containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager); |
843 | if (isset($params['removetag'])) { | 72 | $container = $containerBuilder->build(); |
844 | unset($params['removetag']); | 73 | $app = new App($container); |
845 | } | ||
846 | |||
847 | if (isset($params['searchtags'])) { | ||
848 | $tags = explode(' ', $params['searchtags']); | ||
849 | // Remove value from array $tags. | ||
850 | $tags = array_diff($tags, array($_GET['removetag'])); | ||
851 | $params['searchtags'] = implode(' ', $tags); | ||
852 | |||
853 | if (empty($params['searchtags'])) { | ||
854 | unset($params['searchtags']); | ||
855 | } | ||
856 | |||
857 | // We also remove page (keeping the same page has no sense, since | ||
858 | // the results are different) | ||
859 | unset($params['page']); | ||
860 | } | ||
861 | header('Location: ?'.http_build_query($params)); | ||
862 | exit; | ||
863 | } | ||
864 | |||
865 | // -------- User wants to change the number of bookmarks per page (linksperpage=...) | ||
866 | if (isset($_GET['linksperpage'])) { | ||
867 | if (is_numeric($_GET['linksperpage'])) { | ||
868 | $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage'])); | ||
869 | } | ||
870 | |||
871 | if (! empty($_SERVER['HTTP_REFERER'])) { | ||
872 | $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('linksperpage')); | ||
873 | } else { | ||
874 | $location = '?'; | ||
875 | } | ||
876 | header('Location: '. $location); | ||
877 | exit; | ||
878 | } | ||
879 | |||
880 | // -------- User wants to see only private bookmarks (toggle) | ||
881 | if (isset($_GET['visibility'])) { | ||
882 | if ($_GET['visibility'] === 'private') { | ||
883 | // Visibility not set or not already private, set private, otherwise reset it | ||
884 | if (empty($_SESSION['visibility']) || $_SESSION['visibility'] !== 'private') { | ||
885 | // See only private bookmarks | ||
886 | $_SESSION['visibility'] = 'private'; | ||
887 | } else { | ||
888 | unset($_SESSION['visibility']); | ||
889 | } | ||
890 | } elseif ($_GET['visibility'] === 'public') { | ||
891 | if (empty($_SESSION['visibility']) || $_SESSION['visibility'] !== 'public') { | ||
892 | // See only public bookmarks | ||
893 | $_SESSION['visibility'] = 'public'; | ||
894 | } else { | ||
895 | unset($_SESSION['visibility']); | ||
896 | } | ||
897 | } | ||
898 | |||
899 | if (! empty($_SERVER['HTTP_REFERER'])) { | ||
900 | $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('visibility')); | ||
901 | } else { | ||
902 | $location = '?'; | ||
903 | } | ||
904 | header('Location: '. $location); | ||
905 | exit; | ||
906 | } | ||
907 | |||
908 | // -------- User wants to see only untagged bookmarks (toggle) | ||
909 | if (isset($_GET['untaggedonly'])) { | ||
910 | $_SESSION['untaggedonly'] = empty($_SESSION['untaggedonly']); | ||
911 | |||
912 | if (! empty($_SERVER['HTTP_REFERER'])) { | ||
913 | $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('untaggedonly')); | ||
914 | } else { | ||
915 | $location = '?'; | ||
916 | } | ||
917 | header('Location: '. $location); | ||
918 | exit; | ||
919 | } | ||
920 | |||
921 | // -------- Handle other actions allowed for non-logged in users: | ||
922 | if (!$loginManager->isLoggedIn()) { | ||
923 | // User tries to post new link but is not logged in: | ||
924 | // Show login screen, then redirect to ?post=... | ||
925 | if (isset($_GET['post'])) { | ||
926 | header( // Redirect to login page, then back to post link. | ||
927 | 'Location: /login?post='.urlencode($_GET['post']). | ||
928 | (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):''). | ||
929 | (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):''). | ||
930 | (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):''). | ||
931 | (!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'') | ||
932 | ); | ||
933 | exit; | ||
934 | } | ||
935 | |||
936 | showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); | ||
937 | if (isset($_GET['edit_link'])) { | ||
938 | header('Location: /login?edit_link='. escape($_GET['edit_link'])); | ||
939 | exit; | ||
940 | } | ||
941 | |||
942 | exit; // Never remove this one! All operations below are reserved for logged in user. | ||
943 | } | ||
944 | |||
945 | // -------- All other functions are reserved for the registered user: | ||
946 | |||
947 | // -------- Display the Tools menu if requested (import/export/bookmarklet...) | ||
948 | if ($targetPage == Router::$PAGE_TOOLS) { | ||
949 | $data = [ | ||
950 | 'pageabsaddr' => index_url($_SERVER), | ||
951 | 'sslenabled' => is_https($_SERVER), | ||
952 | ]; | ||
953 | $pluginManager->executeHooks('render_tools', $data); | ||
954 | |||
955 | foreach ($data as $key => $value) { | ||
956 | $PAGE->assign($key, $value); | ||
957 | } | ||
958 | |||
959 | $PAGE->assign('pagetitle', t('Tools') .' - '. $conf->get('general.title', 'Shaarli')); | ||
960 | $PAGE->renderPage('tools'); | ||
961 | exit; | ||
962 | } | ||
963 | |||
964 | // -------- User wants to change his/her password. | ||
965 | if ($targetPage == Router::$PAGE_CHANGEPASSWORD) { | ||
966 | if ($conf->get('security.open_shaarli')) { | ||
967 | die(t('You are not supposed to change a password on an Open Shaarli.')); | ||
968 | } | ||
969 | |||
970 | if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) { | ||
971 | if (!$sessionManager->checkToken($_POST['token'])) { | ||
972 | die(t('Wrong token.')); // Go away! | ||
973 | } | ||
974 | |||
975 | // Make sure old password is correct. | ||
976 | $oldhash = sha1( | ||
977 | $_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt') | ||
978 | ); | ||
979 | if ($oldhash != $conf->get('credentials.hash')) { | ||
980 | echo '<script>alert("' | ||
981 | . t('The old password is not correct.') | ||
982 | .'");document.location=\'?do=changepasswd\';</script>'; | ||
983 | exit; | ||
984 | } | ||
985 | // Save new password | ||
986 | // Salt renders rainbow-tables attacks useless. | ||
987 | $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); | ||
988 | $conf->set( | ||
989 | 'credentials.hash', | ||
990 | sha1( | ||
991 | $_POST['setpassword'] | ||
992 | . $conf->get('credentials.login') | ||
993 | . $conf->get('credentials.salt') | ||
994 | ) | ||
995 | ); | ||
996 | try { | ||
997 | $conf->write($loginManager->isLoggedIn()); | ||
998 | } catch (Exception $e) { | ||
999 | error_log( | ||
1000 | 'ERROR while writing config file after changing password.' . PHP_EOL . | ||
1001 | $e->getMessage() | ||
1002 | ); | ||
1003 | |||
1004 | // TODO: do not handle exceptions/errors in JS. | ||
1005 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>'; | ||
1006 | exit; | ||
1007 | } | ||
1008 | echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>'; | ||
1009 | exit; | ||
1010 | } else { | ||
1011 | // show the change password form. | ||
1012 | $PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1013 | $PAGE->renderPage('changepassword'); | ||
1014 | exit; | ||
1015 | } | ||
1016 | } | ||
1017 | |||
1018 | // -------- User wants to change configuration | ||
1019 | if ($targetPage == Router::$PAGE_CONFIGURE) { | ||
1020 | if (!empty($_POST['title'])) { | ||
1021 | if (!$sessionManager->checkToken($_POST['token'])) { | ||
1022 | die(t('Wrong token.')); // Go away! | ||
1023 | } | ||
1024 | $tz = 'UTC'; | ||
1025 | if (!empty($_POST['continent']) && !empty($_POST['city']) | ||
1026 | && isTimeZoneValid($_POST['continent'], $_POST['city']) | ||
1027 | ) { | ||
1028 | $tz = $_POST['continent'] . '/' . $_POST['city']; | ||
1029 | } | ||
1030 | $conf->set('general.timezone', $tz); | ||
1031 | $conf->set('general.title', escape($_POST['title'])); | ||
1032 | $conf->set('general.header_link', escape($_POST['titleLink'])); | ||
1033 | $conf->set('general.retrieve_description', !empty($_POST['retrieveDescription'])); | ||
1034 | $conf->set('resource.theme', escape($_POST['theme'])); | ||
1035 | $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection'])); | ||
1036 | $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault'])); | ||
1037 | $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks'])); | ||
1038 | $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); | ||
1039 | $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); | ||
1040 | $conf->set('api.enabled', !empty($_POST['enableApi'])); | ||
1041 | $conf->set('api.secret', escape($_POST['apiSecret'])); | ||
1042 | $conf->set('formatter', escape($_POST['formatter'])); | ||
1043 | |||
1044 | if (! empty($_POST['language'])) { | ||
1045 | $conf->set('translation.language', escape($_POST['language'])); | ||
1046 | } | ||
1047 | |||
1048 | $thumbnailsMode = extension_loaded('gd') ? $_POST['enableThumbnails'] : Thumbnailer::MODE_NONE; | ||
1049 | if ($thumbnailsMode !== Thumbnailer::MODE_NONE | ||
1050 | && $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) | ||
1051 | ) { | ||
1052 | $_SESSION['warnings'][] = t( | ||
1053 | 'You have enabled or changed thumbnails mode. ' | ||
1054 | .'<a href="?do=thumbs_update">Please synchronize them</a>.' | ||
1055 | ); | ||
1056 | } | ||
1057 | $conf->set('thumbnails.mode', $thumbnailsMode); | ||
1058 | |||
1059 | try { | ||
1060 | $conf->write($loginManager->isLoggedIn()); | ||
1061 | $history->updateSettings(); | ||
1062 | invalidateCaches($conf->get('resource.page_cache')); | ||
1063 | } catch (Exception $e) { | ||
1064 | error_log( | ||
1065 | 'ERROR while writing config file after configuration update.' . PHP_EOL . | ||
1066 | $e->getMessage() | ||
1067 | ); | ||
1068 | |||
1069 | // TODO: do not handle exceptions/errors in JS. | ||
1070 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>'; | ||
1071 | exit; | ||
1072 | } | ||
1073 | echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>'; | ||
1074 | exit; | ||
1075 | } else { | ||
1076 | // Show the configuration form. | ||
1077 | $PAGE->assign('title', $conf->get('general.title')); | ||
1078 | $PAGE->assign('theme', $conf->get('resource.theme')); | ||
1079 | $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); | ||
1080 | $PAGE->assign('formatter_available', ['default', 'markdown']); | ||
1081 | list($continents, $cities) = generateTimeZoneData( | ||
1082 | timezone_identifiers_list(), | ||
1083 | $conf->get('general.timezone') | ||
1084 | ); | ||
1085 | $PAGE->assign('continents', $continents); | ||
1086 | $PAGE->assign('cities', $cities); | ||
1087 | $PAGE->assign('retrieve_description', $conf->get('general.retrieve_description')); | ||
1088 | $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); | ||
1089 | $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); | ||
1090 | $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); | ||
1091 | $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true)); | ||
1092 | $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); | ||
1093 | $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); | ||
1094 | $PAGE->assign('api_secret', $conf->get('api.secret')); | ||
1095 | $PAGE->assign('languages', Languages::getAvailableLanguages()); | ||
1096 | $PAGE->assign('gd_enabled', extension_loaded('gd')); | ||
1097 | $PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); | ||
1098 | $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1099 | $PAGE->renderPage('configure'); | ||
1100 | exit; | ||
1101 | } | ||
1102 | } | ||
1103 | |||
1104 | // -------- User wants to rename a tag or delete it | ||
1105 | if ($targetPage == Router::$PAGE_CHANGETAG) { | ||
1106 | if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { | ||
1107 | $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : ''); | ||
1108 | $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1109 | $PAGE->renderPage('changetag'); | ||
1110 | exit; | ||
1111 | } | ||
1112 | |||
1113 | if (!$sessionManager->checkToken($_POST['token'])) { | ||
1114 | die(t('Wrong token.')); | ||
1115 | } | ||
1116 | |||
1117 | $toTag = isset($_POST['totag']) ? escape($_POST['totag']) : null; | ||
1118 | $fromTag = escape($_POST['fromtag']); | ||
1119 | $count = 0; | ||
1120 | $bookmarks = $bookmarkService->search(['searchtags' => $fromTag], BookmarkFilter::$ALL, true); | ||
1121 | foreach ($bookmarks as $bookmark) { | ||
1122 | if ($toTag) { | ||
1123 | $bookmark->renameTag($fromTag, $toTag); | ||
1124 | } else { | ||
1125 | $bookmark->deleteTag($fromTag); | ||
1126 | } | ||
1127 | $bookmarkService->set($bookmark, false); | ||
1128 | $history->updateLink($bookmark); | ||
1129 | $count++; | ||
1130 | } | ||
1131 | $bookmarkService->save(); | ||
1132 | $delete = empty($_POST['totag']); | ||
1133 | $redirect = $delete ? 'do=changetag' : 'searchtags='. urlencode(escape($_POST['totag'])); | ||
1134 | $alert = $delete | ||
1135 | ? sprintf(t('The tag was removed from %d link.', 'The tag was removed from %d bookmarks.', $count), $count) | ||
1136 | : sprintf(t('The tag was renamed in %d link.', 'The tag was renamed in %d bookmarks.', $count), $count); | ||
1137 | echo '<script>alert("'. $alert .'");document.location=\'?'. $redirect .'\';</script>'; | ||
1138 | exit; | ||
1139 | } | ||
1140 | |||
1141 | // -------- User wants to add a link without using the bookmarklet: Show form. | ||
1142 | if ($targetPage == Router::$PAGE_ADDLINK) { | ||
1143 | $PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1144 | $PAGE->renderPage('addlink'); | ||
1145 | exit; | ||
1146 | } | ||
1147 | |||
1148 | // -------- User clicked the "Save" button when editing a link: Save link to database. | ||
1149 | if (isset($_POST['save_edit'])) { | ||
1150 | // Go away! | ||
1151 | if (! $sessionManager->checkToken($_POST['token'])) { | ||
1152 | die(t('Wrong token.')); | ||
1153 | } | ||
1154 | |||
1155 | // lf_id should only be present if the link exists. | ||
1156 | $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : null; | ||
1157 | if ($id && $bookmarkService->exists($id)) { | ||
1158 | // Edit | ||
1159 | $bookmark = $bookmarkService->get($id); | ||
1160 | } else { | ||
1161 | // New link | ||
1162 | $bookmark = new Bookmark(); | ||
1163 | } | ||
1164 | |||
1165 | $bookmark->setTitle($_POST['lf_title']); | ||
1166 | $bookmark->setDescription($_POST['lf_description']); | ||
1167 | $bookmark->setUrl($_POST['lf_url'], $conf->get('security.allowed_protocols')); | ||
1168 | $bookmark->setPrivate(isset($_POST['lf_private'])); | ||
1169 | $bookmark->setTagsString($_POST['lf_tags']); | ||
1170 | |||
1171 | if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
1172 | && ! $bookmark->isNote() | ||
1173 | ) { | ||
1174 | $thumbnailer = new Thumbnailer($conf); | ||
1175 | $bookmark->setThumbnail($thumbnailer->get($bookmark->getUrl())); | ||
1176 | } | ||
1177 | $bookmarkService->addOrSet($bookmark, false); | ||
1178 | |||
1179 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
1180 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1181 | $formatter = $factory->getFormatter('raw'); | ||
1182 | $data = $formatter->format($bookmark); | ||
1183 | $pluginManager->executeHooks('save_link', $data); | ||
1184 | |||
1185 | $bookmark->fromArray($data); | ||
1186 | $bookmarkService->set($bookmark); | ||
1187 | |||
1188 | // If we are called from the bookmarklet, we must close the popup: | ||
1189 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | ||
1190 | echo '<script>self.close();</script>'; | ||
1191 | exit; | ||
1192 | } | ||
1193 | |||
1194 | $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?'; | ||
1195 | $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); | ||
1196 | // Scroll to the link which has been edited. | ||
1197 | $location .= '#' . $bookmark->getShortUrl(); | ||
1198 | // After saving the link, redirect to the page the user was on. | ||
1199 | header('Location: '. $location); | ||
1200 | exit; | ||
1201 | } | ||
1202 | |||
1203 | // -------- User clicked the "Delete" button when editing a link: Delete link from database. | ||
1204 | if ($targetPage == Router::$PAGE_DELETELINK) { | ||
1205 | if (! $sessionManager->checkToken($_GET['token'])) { | ||
1206 | die(t('Wrong token.')); | ||
1207 | } | ||
1208 | |||
1209 | $ids = trim($_GET['lf_linkdate']); | ||
1210 | if (strpos($ids, ' ') !== false) { | ||
1211 | // multiple, space-separated ids provided | ||
1212 | $ids = array_values(array_filter( | ||
1213 | preg_split('/\s+/', escape($ids)), | ||
1214 | function ($item) { | ||
1215 | return $item !== ''; | ||
1216 | } | ||
1217 | )); | ||
1218 | } else { | ||
1219 | // only a single id provided | ||
1220 | $shortUrl = $bookmarkService->get($ids)->getShortUrl(); | ||
1221 | $ids = [$ids]; | ||
1222 | } | ||
1223 | // assert at least one id is given | ||
1224 | if (!count($ids)) { | ||
1225 | die('no id provided'); | ||
1226 | } | ||
1227 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1228 | $formatter = $factory->getFormatter('raw'); | ||
1229 | foreach ($ids as $id) { | ||
1230 | $id = (int) escape($id); | ||
1231 | $bookmark = $bookmarkService->get($id); | ||
1232 | $data = $formatter->format($bookmark); | ||
1233 | $pluginManager->executeHooks('delete_link', $data); | ||
1234 | $bookmarkService->remove($bookmark, false); | ||
1235 | } | ||
1236 | $bookmarkService->save(); | ||
1237 | |||
1238 | // If we are called from the bookmarklet, we must close the popup: | ||
1239 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | ||
1240 | echo '<script>self.close();</script>'; | ||
1241 | exit; | ||
1242 | } | ||
1243 | |||
1244 | $location = '?'; | ||
1245 | if (isset($_SERVER['HTTP_REFERER'])) { | ||
1246 | // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404. | ||
1247 | $location = generateLocation( | ||
1248 | $_SERVER['HTTP_REFERER'], | ||
1249 | $_SERVER['HTTP_HOST'], | ||
1250 | ['delete_link', 'edit_link', ! empty($shortUrl) ? $shortUrl : null] | ||
1251 | ); | ||
1252 | } | ||
1253 | |||
1254 | header('Location: ' . $location); // After deleting the link, redirect to appropriate location | ||
1255 | exit; | ||
1256 | } | ||
1257 | |||
1258 | // -------- User clicked either "Set public" or "Set private" bulk operation | ||
1259 | if ($targetPage == Router::$PAGE_CHANGE_VISIBILITY) { | ||
1260 | if (! $sessionManager->checkToken($_GET['token'])) { | ||
1261 | die(t('Wrong token.')); | ||
1262 | } | ||
1263 | |||
1264 | $ids = trim($_GET['ids']); | ||
1265 | if (strpos($ids, ' ') !== false) { | ||
1266 | // multiple, space-separated ids provided | ||
1267 | $ids = array_values(array_filter(preg_split('/\s+/', escape($ids)))); | ||
1268 | } else { | ||
1269 | // only a single id provided | ||
1270 | $ids = [$ids]; | ||
1271 | } | ||
1272 | |||
1273 | // assert at least one id is given | ||
1274 | if (!count($ids)) { | ||
1275 | die('no id provided'); | ||
1276 | } | ||
1277 | // assert that the visibility is valid | ||
1278 | if (!isset($_GET['newVisibility']) || !in_array($_GET['newVisibility'], ['public', 'private'])) { | ||
1279 | die('invalid visibility'); | ||
1280 | } else { | ||
1281 | $private = $_GET['newVisibility'] === 'private'; | ||
1282 | } | ||
1283 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1284 | $formatter = $factory->getFormatter('raw'); | ||
1285 | foreach ($ids as $id) { | ||
1286 | $id = (int) escape($id); | ||
1287 | $bookmark = $bookmarkService->get($id); | ||
1288 | $bookmark->setPrivate($private); | ||
1289 | |||
1290 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
1291 | $data = $formatter->format($bookmark); | ||
1292 | $pluginManager->executeHooks('save_link', $data); | ||
1293 | $bookmark->fromArray($data); | ||
1294 | |||
1295 | $bookmarkService->set($bookmark); | ||
1296 | } | ||
1297 | $bookmarkService->save(); | ||
1298 | |||
1299 | $location = '?'; | ||
1300 | if (isset($_SERVER['HTTP_REFERER'])) { | ||
1301 | $location = generateLocation( | ||
1302 | $_SERVER['HTTP_REFERER'], | ||
1303 | $_SERVER['HTTP_HOST'] | ||
1304 | ); | ||
1305 | } | ||
1306 | header('Location: ' . $location); // After deleting the link, redirect to appropriate location | ||
1307 | exit; | ||
1308 | } | ||
1309 | |||
1310 | // -------- User clicked the "EDIT" button on a link: Display link edit form. | ||
1311 | if (isset($_GET['edit_link'])) { | ||
1312 | $id = (int) escape($_GET['edit_link']); | ||
1313 | try { | ||
1314 | $link = $bookmarkService->get($id); // Read database | ||
1315 | } catch (BookmarkNotFoundException $e) { | ||
1316 | // Link not found in database. | ||
1317 | header('Location: ?'); | ||
1318 | exit; | ||
1319 | } | ||
1320 | |||
1321 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1322 | $formatter = $factory->getFormatter('raw'); | ||
1323 | $formattedLink = $formatter->format($link); | ||
1324 | $tags = $bookmarkService->bookmarksCountPerTag(); | ||
1325 | if ($conf->get('formatter') === 'markdown') { | ||
1326 | $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; | ||
1327 | } | ||
1328 | $data = array( | ||
1329 | 'link' => $formattedLink, | ||
1330 | 'link_is_new' => false, | ||
1331 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), | ||
1332 | 'tags' => $tags, | ||
1333 | ); | ||
1334 | $pluginManager->executeHooks('render_editlink', $data); | ||
1335 | |||
1336 | foreach ($data as $key => $value) { | ||
1337 | $PAGE->assign($key, $value); | ||
1338 | } | ||
1339 | |||
1340 | $PAGE->assign('pagetitle', t('Edit') .' '. t('Shaare') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1341 | $PAGE->renderPage('editlink'); | ||
1342 | exit; | ||
1343 | } | ||
1344 | |||
1345 | // -------- User want to post a new link: Display link edit form. | ||
1346 | if (isset($_GET['post'])) { | ||
1347 | $url = cleanup_url($_GET['post']); | ||
1348 | |||
1349 | $link_is_new = false; | ||
1350 | // Check if URL is not already in database (in this case, we will edit the existing link) | ||
1351 | $bookmark = $bookmarkService->findByUrl($url); | ||
1352 | if (! $bookmark) { | ||
1353 | $link_is_new = true; | ||
1354 | // Get title if it was provided in URL (by the bookmarklet). | ||
1355 | $title = empty($_GET['title']) ? '' : escape($_GET['title']); | ||
1356 | // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] | ||
1357 | $description = empty($_GET['description']) ? '' : escape($_GET['description']); | ||
1358 | $tags = empty($_GET['tags']) ? '' : escape($_GET['tags']); | ||
1359 | $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0; | ||
1360 | |||
1361 | // If this is an HTTP(S) link, we try go get the page to extract | ||
1362 | // the title (otherwise we will to straight to the edit form.) | ||
1363 | if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { | ||
1364 | $retrieveDescription = $conf->get('general.retrieve_description'); | ||
1365 | // Short timeout to keep the application responsive | ||
1366 | // The callback will fill $charset and $title with data from the downloaded page. | ||
1367 | get_http_response( | ||
1368 | $url, | ||
1369 | $conf->get('general.download_timeout', 30), | ||
1370 | $conf->get('general.download_max_size', 4194304), | ||
1371 | get_curl_download_callback($charset, $title, $description, $tags, $retrieveDescription) | ||
1372 | ); | ||
1373 | if (! empty($title) && strtolower($charset) != 'utf-8') { | ||
1374 | $title = mb_convert_encoding($title, 'utf-8', $charset); | ||
1375 | } | ||
1376 | } | ||
1377 | |||
1378 | if ($url == '') { | ||
1379 | $title = $conf->get('general.default_note_title', t('Note: ')); | ||
1380 | } | ||
1381 | $url = escape($url); | ||
1382 | $title = escape($title); | ||
1383 | |||
1384 | $link = [ | ||
1385 | 'title' => $title, | ||
1386 | 'url' => $url, | ||
1387 | 'description' => $description, | ||
1388 | 'tags' => $tags, | ||
1389 | 'private' => $private, | ||
1390 | ]; | ||
1391 | } else { | ||
1392 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1393 | $formatter = $factory->getFormatter('raw'); | ||
1394 | $link = $formatter->format($bookmark); | ||
1395 | } | ||
1396 | |||
1397 | $tags = $bookmarkService->bookmarksCountPerTag(); | ||
1398 | if ($conf->get('formatter') === 'markdown') { | ||
1399 | $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; | ||
1400 | } | ||
1401 | $data = [ | ||
1402 | 'link' => $link, | ||
1403 | 'link_is_new' => $link_is_new, | ||
1404 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), | ||
1405 | 'source' => (isset($_GET['source']) ? $_GET['source'] : ''), | ||
1406 | 'tags' => $tags, | ||
1407 | 'default_private_links' => $conf->get('privacy.default_private_links', false), | ||
1408 | ]; | ||
1409 | $pluginManager->executeHooks('render_editlink', $data); | ||
1410 | |||
1411 | foreach ($data as $key => $value) { | ||
1412 | $PAGE->assign($key, $value); | ||
1413 | } | ||
1414 | |||
1415 | $PAGE->assign('pagetitle', t('Shaare') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1416 | $PAGE->renderPage('editlink'); | ||
1417 | exit; | ||
1418 | } | ||
1419 | |||
1420 | if ($targetPage == Router::$PAGE_PINLINK) { | ||
1421 | if (! isset($_GET['id']) || !$bookmarkService->exists($_GET['id'])) { | ||
1422 | // FIXME! Use a proper error system. | ||
1423 | $msg = t('Invalid link ID provided'); | ||
1424 | echo '<script>alert("'. $msg .'");document.location=\''. index_url($_SERVER) .'\';</script>'; | ||
1425 | exit; | ||
1426 | } | ||
1427 | if (! $sessionManager->checkToken($_GET['token'])) { | ||
1428 | die('Wrong token.'); | ||
1429 | } | ||
1430 | |||
1431 | $link = $bookmarkService->get($_GET['id']); | ||
1432 | $link->setSticky(! $link->isSticky()); | ||
1433 | $bookmarkService->set($link); | ||
1434 | header('Location: '.index_url($_SERVER)); | ||
1435 | exit; | ||
1436 | } | ||
1437 | |||
1438 | if ($targetPage == Router::$PAGE_EXPORT) { | ||
1439 | // Export bookmarks 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 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1457 | $formatter = $factory->getFormatter('raw'); | ||
1458 | $PAGE->assign( | ||
1459 | 'links', | ||
1460 | NetscapeBookmarkUtils::filterAndFormat( | ||
1461 | $bookmarkService, | ||
1462 | $formatter, | ||
1463 | $selection, | ||
1464 | $prependNoteUrl, | ||
1465 | index_url($_SERVER) | ||
1466 | ) | ||
1467 | ); | ||
1468 | } catch (Exception $exc) { | ||
1469 | header('Content-Type: text/plain; charset=utf-8'); | ||
1470 | echo $exc->getMessage(); | ||
1471 | exit; | ||
1472 | } | ||
1473 | $now = new DateTime(); | ||
1474 | header('Content-Type: text/html; charset=utf-8'); | ||
1475 | header( | ||
1476 | 'Content-disposition: attachment; filename=bookmarks_' | ||
1477 | .$selection.'_'.$now->format(Bookmark::LINK_DATE_FORMAT).'.html' | ||
1478 | ); | ||
1479 | $PAGE->assign('date', $now->format(DateTime::RFC822)); | ||
1480 | $PAGE->assign('eol', PHP_EOL); | ||
1481 | $PAGE->assign('selection', $selection); | ||
1482 | $PAGE->renderPage('export.bookmarks'); | ||
1483 | exit; | ||
1484 | } | ||
1485 | |||
1486 | if ($targetPage == Router::$PAGE_IMPORT) { | ||
1487 | // Upload a Netscape bookmark dump to import its contents | ||
1488 | |||
1489 | if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) { | ||
1490 | // Show import dialog | ||
1491 | $PAGE->assign( | ||
1492 | 'maxfilesize', | ||
1493 | get_max_upload_size( | ||
1494 | ini_get('post_max_size'), | ||
1495 | ini_get('upload_max_filesize'), | ||
1496 | false | ||
1497 | ) | ||
1498 | ); | ||
1499 | $PAGE->assign( | ||
1500 | 'maxfilesizeHuman', | ||
1501 | get_max_upload_size( | ||
1502 | ini_get('post_max_size'), | ||
1503 | ini_get('upload_max_filesize'), | ||
1504 | true | ||
1505 | ) | ||
1506 | ); | ||
1507 | $PAGE->assign('pagetitle', t('Import') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1508 | $PAGE->renderPage('import'); | ||
1509 | exit; | ||
1510 | } | ||
1511 | |||
1512 | // Import bookmarks from an uploaded file | ||
1513 | if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) { | ||
1514 | // The file is too big or some form field may be missing. | ||
1515 | $msg = sprintf( | ||
1516 | t( | ||
1517 | 'The file you are trying to upload is probably bigger than what this webserver can accept' | ||
1518 | .' (%s). Please upload in smaller chunks.' | ||
1519 | ), | ||
1520 | get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')) | ||
1521 | ); | ||
1522 | echo '<script>alert("'. $msg .'");document.location=\'?do='.Router::$PAGE_IMPORT .'\';</script>'; | ||
1523 | exit; | ||
1524 | } | ||
1525 | if (! $sessionManager->checkToken($_POST['token'])) { | ||
1526 | die('Wrong token.'); | ||
1527 | } | ||
1528 | $status = NetscapeBookmarkUtils::import( | ||
1529 | $_POST, | ||
1530 | $_FILES, | ||
1531 | $bookmarkService, | ||
1532 | $conf, | ||
1533 | $history | ||
1534 | ); | ||
1535 | echo '<script>alert("'.$status.'");document.location=\'?do=' | ||
1536 | .Router::$PAGE_IMPORT .'\';</script>'; | ||
1537 | exit; | ||
1538 | } | ||
1539 | |||
1540 | // Plugin administration page | ||
1541 | if ($targetPage == Router::$PAGE_PLUGINSADMIN) { | ||
1542 | $pluginMeta = $pluginManager->getPluginsMeta(); | ||
1543 | |||
1544 | // Split plugins into 2 arrays: ordered enabled plugins and disabled. | ||
1545 | $enabledPlugins = array_filter($pluginMeta, function ($v) { | ||
1546 | return $v['order'] !== false; | ||
1547 | }); | ||
1548 | // Load parameters. | ||
1549 | $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array())); | ||
1550 | uasort( | ||
1551 | $enabledPlugins, | ||
1552 | function ($a, $b) { | ||
1553 | return $a['order'] - $b['order']; | ||
1554 | } | ||
1555 | ); | ||
1556 | $disabledPlugins = array_filter($pluginMeta, function ($v) { | ||
1557 | return $v['order'] === false; | ||
1558 | }); | ||
1559 | |||
1560 | $PAGE->assign('enabledPlugins', $enabledPlugins); | ||
1561 | $PAGE->assign('disabledPlugins', $disabledPlugins); | ||
1562 | $PAGE->assign('pagetitle', t('Plugin administration') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1563 | $PAGE->renderPage('pluginsadmin'); | ||
1564 | exit; | ||
1565 | } | ||
1566 | |||
1567 | // Plugin administration form action | ||
1568 | if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { | ||
1569 | try { | ||
1570 | if (isset($_POST['parameters_form'])) { | ||
1571 | $pluginManager->executeHooks('save_plugin_parameters', $_POST); | ||
1572 | unset($_POST['parameters_form']); | ||
1573 | foreach ($_POST as $param => $value) { | ||
1574 | $conf->set('plugins.'. $param, escape($value)); | ||
1575 | } | ||
1576 | } else { | ||
1577 | $conf->set('general.enabled_plugins', save_plugin_config($_POST)); | ||
1578 | } | ||
1579 | $conf->write($loginManager->isLoggedIn()); | ||
1580 | $history->updateSettings(); | ||
1581 | } catch (Exception $e) { | ||
1582 | error_log( | ||
1583 | 'ERROR while saving plugin configuration:.' . PHP_EOL . | ||
1584 | $e->getMessage() | ||
1585 | ); | ||
1586 | |||
1587 | // TODO: do not handle exceptions/errors in JS. | ||
1588 | echo '<script>alert("' | ||
1589 | . $e->getMessage() | ||
1590 | .'");document.location=\'?do=' | ||
1591 | . Router::$PAGE_PLUGINSADMIN | ||
1592 | .'\';</script>'; | ||
1593 | exit; | ||
1594 | } | ||
1595 | header('Location: ?do='. Router::$PAGE_PLUGINSADMIN); | ||
1596 | exit; | ||
1597 | } | ||
1598 | |||
1599 | // Get a fresh token | ||
1600 | if ($targetPage == Router::$GET_TOKEN) { | ||
1601 | header('Content-Type:text/plain'); | ||
1602 | echo $sessionManager->generateToken(); | ||
1603 | exit; | ||
1604 | } | ||
1605 | |||
1606 | // -------- Thumbnails Update | ||
1607 | if ($targetPage == Router::$PAGE_THUMBS_UPDATE) { | ||
1608 | $ids = []; | ||
1609 | foreach ($bookmarkService->search() as $bookmark) { | ||
1610 | // A note or not HTTP(S) | ||
1611 | if ($bookmark->isNote() || ! startsWith(strtolower($bookmark->getUrl()), 'http')) { | ||
1612 | continue; | ||
1613 | } | ||
1614 | $ids[] = $bookmark->getId(); | ||
1615 | } | ||
1616 | $PAGE->assign('ids', $ids); | ||
1617 | $PAGE->assign('pagetitle', t('Thumbnails update') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1618 | $PAGE->renderPage('thumbnails'); | ||
1619 | exit; | ||
1620 | } | ||
1621 | |||
1622 | // -------- Single Thumbnail Update | ||
1623 | if ($targetPage == Router::$AJAX_THUMB_UPDATE) { | ||
1624 | if (! isset($_POST['id']) || ! ctype_digit($_POST['id'])) { | ||
1625 | http_response_code(400); | ||
1626 | exit; | ||
1627 | } | ||
1628 | $id = (int) $_POST['id']; | ||
1629 | if (! $bookmarkService->exists($id)) { | ||
1630 | http_response_code(404); | ||
1631 | exit; | ||
1632 | } | ||
1633 | $thumbnailer = new Thumbnailer($conf); | ||
1634 | $bookmark = $bookmarkService->get($id); | ||
1635 | $bookmark->setThumbnail($thumbnailer->get($bookmark->getUrl())); | ||
1636 | $bookmarkService->set($bookmark); | ||
1637 | |||
1638 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1639 | echo json_encode($factory->getFormatter('raw')->format($bookmark)); | ||
1640 | exit; | ||
1641 | } | ||
1642 | |||
1643 | // -------- Otherwise, simply display search form and bookmarks: | ||
1644 | showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); | ||
1645 | exit; | ||
1646 | } | ||
1647 | |||
1648 | /** | ||
1649 | * Template for the list of bookmarks (<div id="linklist">) | ||
1650 | * This function fills all the necessary fields in the $PAGE for the template 'linklist.html' | ||
1651 | * | ||
1652 | * @param pageBuilder $PAGE pageBuilder instance. | ||
1653 | * @param BookmarkServiceInterface $linkDb LinkDB instance. | ||
1654 | * @param ConfigManager $conf Configuration Manager instance. | ||
1655 | * @param PluginManager $pluginManager Plugin Manager instance. | ||
1656 | * @param LoginManager $loginManager LoginManager instance | ||
1657 | */ | ||
1658 | function buildLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager) | ||
1659 | { | ||
1660 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
1661 | $formatter = $factory->getFormatter(); | ||
1662 | |||
1663 | // Used in templates | ||
1664 | if (isset($_GET['searchtags'])) { | ||
1665 | if (! empty($_GET['searchtags'])) { | ||
1666 | $searchtags = escape(normalize_spaces($_GET['searchtags'])); | ||
1667 | } else { | ||
1668 | $searchtags = false; | ||
1669 | } | ||
1670 | } else { | ||
1671 | $searchtags = ''; | ||
1672 | } | ||
1673 | $searchterm = !empty($_GET['searchterm']) ? escape(normalize_spaces($_GET['searchterm'])) : ''; | ||
1674 | |||
1675 | // Smallhash filter | ||
1676 | if (! empty($_SERVER['QUERY_STRING']) | ||
1677 | && preg_match('/^[a-zA-Z0-9-_@]{6}($|&|#)/', $_SERVER['QUERY_STRING'])) { | ||
1678 | try { | ||
1679 | $linksToDisplay = $linkDb->findByHash($_SERVER['QUERY_STRING']); | ||
1680 | } catch (BookmarkNotFoundException $e) { | ||
1681 | $PAGE->render404($e->getMessage()); | ||
1682 | exit; | ||
1683 | } | ||
1684 | } else { | ||
1685 | // Filter bookmarks according search parameters. | ||
1686 | $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : null; | ||
1687 | $request = [ | ||
1688 | 'searchtags' => $searchtags, | ||
1689 | 'searchterm' => $searchterm, | ||
1690 | ]; | ||
1691 | $linksToDisplay = $linkDb->search($request, $visibility, false, !empty($_SESSION['untaggedonly'])); | ||
1692 | } | ||
1693 | |||
1694 | // ---- Handle paging. | ||
1695 | $keys = array(); | ||
1696 | foreach ($linksToDisplay as $key => $value) { | ||
1697 | $keys[] = $key; | ||
1698 | } | ||
1699 | |||
1700 | // Select articles according to paging. | ||
1701 | $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); | ||
1702 | $pagecount = $pagecount == 0 ? 1 : $pagecount; | ||
1703 | $page= empty($_GET['page']) ? 1 : intval($_GET['page']); | ||
1704 | $page = $page < 1 ? 1 : $page; | ||
1705 | $page = $page > $pagecount ? $pagecount : $page; | ||
1706 | // Start index. | ||
1707 | $i = ($page-1) * $_SESSION['LINKS_PER_PAGE']; | ||
1708 | $end = $i + $_SESSION['LINKS_PER_PAGE']; | ||
1709 | |||
1710 | $thumbnailsEnabled = $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE; | ||
1711 | if ($thumbnailsEnabled) { | ||
1712 | $thumbnailer = new Thumbnailer($conf); | ||
1713 | } | ||
1714 | |||
1715 | $linkDisp = array(); | ||
1716 | while ($i<$end && $i<count($keys)) { | ||
1717 | $link = $formatter->format($linksToDisplay[$keys[$i]]); | ||
1718 | |||
1719 | // Logged in, thumbnails enabled, not a note, | ||
1720 | // and (never retrieved yet or no valid cache file) | ||
1721 | if ($loginManager->isLoggedIn() | ||
1722 | && $thumbnailsEnabled | ||
1723 | && !$linksToDisplay[$keys[$i]]->isNote() | ||
1724 | && $linksToDisplay[$keys[$i]]->getThumbnail() !== false | ||
1725 | && ! is_file($linksToDisplay[$keys[$i]]->getThumbnail()) | ||
1726 | ) { | ||
1727 | $linksToDisplay[$keys[$i]]->setThumbnail($thumbnailer->get($link['url'])); | ||
1728 | $linkDb->set($linksToDisplay[$keys[$i]], false); | ||
1729 | $updateDB = true; | ||
1730 | $link['thumbnail'] = $linksToDisplay[$keys[$i]]->getThumbnail(); | ||
1731 | } | ||
1732 | |||
1733 | // Check for both signs of a note: starting with ? and 7 chars long. | ||
1734 | // if ($link['url'][0] === '?' && strlen($link['url']) === 7) { | ||
1735 | // $link['url'] = index_url($_SERVER) . $link['url']; | ||
1736 | // } | ||
1737 | |||
1738 | $linkDisp[$keys[$i]] = $link; | ||
1739 | $i++; | ||
1740 | } | ||
1741 | |||
1742 | // If we retrieved new thumbnails, we update the database. | ||
1743 | if (!empty($updateDB)) { | ||
1744 | $linkDb->save(); | ||
1745 | } | ||
1746 | 74 | ||
1747 | // Compute paging navigation | 75 | // Main Shaarli routes |
1748 | $searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags); | 76 | $app->group('', function () { |
1749 | $searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm); | 77 | $this->get('/install', '\Shaarli\Front\Controller\Visitor\InstallController:index')->setName('displayInstall'); |
1750 | $previous_page_url = ''; | 78 | $this->get('/install/session-test', '\Shaarli\Front\Controller\Visitor\InstallController:sessionTest'); |
1751 | if ($i != count($keys)) { | 79 | $this->post('/install', '\Shaarli\Front\Controller\Visitor\InstallController:save')->setName('saveInstall'); |
1752 | $previous_page_url = '?page=' . ($page+1) . $searchtermUrl . $searchtagsUrl; | 80 | |
1753 | } | 81 | /* -- PUBLIC --*/ |
1754 | $next_page_url=''; | 82 | $this->get('/', '\Shaarli\Front\Controller\Visitor\BookmarkListController:index'); |
1755 | if ($page>1) { | 83 | $this->get('/shaare/{hash}', '\Shaarli\Front\Controller\Visitor\BookmarkListController:permalink'); |
1756 | $next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl; | 84 | $this->get('/login', '\Shaarli\Front\Controller\Visitor\LoginController:index')->setName('login'); |
1757 | } | 85 | $this->post('/login', '\Shaarli\Front\Controller\Visitor\LoginController:login')->setName('processLogin'); |
86 | $this->get('/picture-wall', '\Shaarli\Front\Controller\Visitor\PictureWallController:index'); | ||
87 | $this->get('/tags/cloud', '\Shaarli\Front\Controller\Visitor\TagCloudController:cloud'); | ||
88 | $this->get('/tags/list', '\Shaarli\Front\Controller\Visitor\TagCloudController:list'); | ||
89 | $this->get('/daily', '\Shaarli\Front\Controller\Visitor\DailyController:index'); | ||
90 | $this->get('/daily-rss', '\Shaarli\Front\Controller\Visitor\DailyController:rss')->setName('rss'); | ||
91 | $this->get('/feed/atom', '\Shaarli\Front\Controller\Visitor\FeedController:atom')->setName('atom'); | ||
92 | $this->get('/feed/rss', '\Shaarli\Front\Controller\Visitor\FeedController:rss'); | ||
93 | $this->get('/open-search', '\Shaarli\Front\Controller\Visitor\OpenSearchController:index'); | ||
94 | |||
95 | $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\Visitor\TagController:addTag'); | ||
96 | $this->get('/remove-tag/{tag}', '\Shaarli\Front\Controller\Visitor\TagController:removeTag'); | ||
97 | $this->get('/links-per-page', '\Shaarli\Front\Controller\Visitor\PublicSessionFilterController:linksPerPage'); | ||
98 | $this->get('/untagged-only', '\Shaarli\Front\Controller\Admin\PublicSessionFilterController:untaggedOnly'); | ||
99 | })->add('\Shaarli\Front\ShaarliMiddleware'); | ||
1758 | 100 | ||
1759 | // Fill all template fields. | 101 | $app->group('/admin', function () { |
1760 | $data = array( | 102 | $this->get('/logout', '\Shaarli\Front\Controller\Admin\LogoutController:index'); |
1761 | 'previous_page_url' => $previous_page_url, | 103 | $this->get('/tools', '\Shaarli\Front\Controller\Admin\ToolsController:index'); |
1762 | 'next_page_url' => $next_page_url, | 104 | $this->get('/password', '\Shaarli\Front\Controller\Admin\PasswordController:index'); |
1763 | 'page_current' => $page, | 105 | $this->post('/password', '\Shaarli\Front\Controller\Admin\PasswordController:change'); |
1764 | 'page_max' => $pagecount, | 106 | $this->get('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:index'); |
1765 | 'result_count' => count($linksToDisplay), | 107 | $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save'); |
1766 | 'search_term' => $searchterm, | 108 | $this->get('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index'); |
1767 | 'search_tags' => $searchtags, | 109 | $this->post('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save'); |
1768 | 'visibility' => ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '', | 110 | $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:addShaare'); |
1769 | 'links' => $linkDisp, | 111 | $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayCreateForm'); |
112 | $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayEditForm'); | ||
113 | $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save'); | ||
114 | $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark'); | ||
115 | $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility'); | ||
116 | $this->get('/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark'); | ||
117 | $this->patch( | ||
118 | '/shaare/{id:[0-9]+}/update-thumbnail', | ||
119 | '\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate' | ||
1770 | ); | 120 | ); |
121 | $this->get('/export', '\Shaarli\Front\Controller\Admin\ExportController:index'); | ||
122 | $this->post('/export', '\Shaarli\Front\Controller\Admin\ExportController:export'); | ||
123 | $this->get('/import', '\Shaarli\Front\Controller\Admin\ImportController:index'); | ||
124 | $this->post('/import', '\Shaarli\Front\Controller\Admin\ImportController:import'); | ||
125 | $this->get('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index'); | ||
126 | $this->post('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save'); | ||
127 | $this->get('/token', '\Shaarli\Front\Controller\Admin\TokenController:getToken'); | ||
128 | $this->get('/thumbnails', '\Shaarli\Front\Controller\Admin\ThumbnailsController:index'); | ||
1771 | 129 | ||
1772 | // If there is only a single link, we change on-the-fly the title of the page. | 130 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); |
1773 | if (count($linksToDisplay) == 1) { | 131 | })->add('\Shaarli\Front\ShaarliAdminMiddleware'); |
1774 | $data['pagetitle'] = $linksToDisplay[$keys[0]]->getTitle() .' - '. $conf->get('general.title'); | ||
1775 | } elseif (! empty($searchterm) || ! empty($searchtags)) { | ||
1776 | $data['pagetitle'] = t('Search: '); | ||
1777 | $data['pagetitle'] .= ! empty($searchterm) ? $searchterm .' ' : ''; | ||
1778 | $bracketWrap = function ($tag) { | ||
1779 | return '['. $tag .']'; | ||
1780 | }; | ||
1781 | $data['pagetitle'] .= ! empty($searchtags) | ||
1782 | ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchtags))).' ' | ||
1783 | : ''; | ||
1784 | $data['pagetitle'] .= '- '. $conf->get('general.title'); | ||
1785 | } | ||
1786 | |||
1787 | $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => $loginManager->isLoggedIn())); | ||
1788 | |||
1789 | foreach ($data as $key => $value) { | ||
1790 | $PAGE->assign($key, $value); | ||
1791 | } | ||
1792 | |||
1793 | return; | ||
1794 | } | ||
1795 | |||
1796 | /** | ||
1797 | * Installation | ||
1798 | * This function should NEVER be called if the file data/config.php exists. | ||
1799 | * | ||
1800 | * @param ConfigManager $conf Configuration Manager instance. | ||
1801 | * @param SessionManager $sessionManager SessionManager instance | ||
1802 | * @param LoginManager $loginManager LoginManager instance | ||
1803 | */ | ||
1804 | function install($conf, $sessionManager, $loginManager) | ||
1805 | { | ||
1806 | // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. | ||
1807 | if (endsWith($_SERVER['HTTP_HOST'], '.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) { | ||
1808 | mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions', 0705); | ||
1809 | } | ||
1810 | |||
1811 | |||
1812 | // This part makes sure sessions works correctly. | ||
1813 | // (Because on some hosts, session.save_path may not be set correctly, | ||
1814 | // or we may not have write access to it.) | ||
1815 | if (isset($_GET['test_session']) | ||
1816 | && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) { | ||
1817 | // Step 2: Check if data in session is correct. | ||
1818 | $msg = t( | ||
1819 | '<pre>Sessions do not seem to work correctly on your server.<br>'. | ||
1820 | 'Make sure the variable "session.save_path" is set correctly in your PHP config, '. | ||
1821 | 'and that you have write access to it.<br>'. | ||
1822 | 'It currently points to %s.<br>'. | ||
1823 | 'On some browsers, accessing your server via a hostname like \'localhost\' '. | ||
1824 | 'or any custom hostname without a dot causes cookie storage to fail. '. | ||
1825 | 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>' | ||
1826 | ); | ||
1827 | $msg = sprintf($msg, session_save_path()); | ||
1828 | echo $msg; | ||
1829 | echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>'; | ||
1830 | die; | ||
1831 | } | ||
1832 | if (!isset($_SESSION['session_tested'])) { | ||
1833 | // Step 1 : Try to store data in session and reload page. | ||
1834 | $_SESSION['session_tested'] = 'Working'; // Try to set a variable in session. | ||
1835 | header('Location: '.index_url($_SERVER).'?test_session'); // Redirect to check stored data. | ||
1836 | } | ||
1837 | if (isset($_GET['test_session'])) { | ||
1838 | // Step 3: Sessions are OK. Remove test parameter from URL. | ||
1839 | header('Location: '.index_url($_SERVER)); | ||
1840 | } | ||
1841 | |||
1842 | |||
1843 | if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) { | ||
1844 | $tz = 'UTC'; | ||
1845 | if (!empty($_POST['continent']) && !empty($_POST['city']) | ||
1846 | && isTimeZoneValid($_POST['continent'], $_POST['city']) | ||
1847 | ) { | ||
1848 | $tz = $_POST['continent'].'/'.$_POST['city']; | ||
1849 | } | ||
1850 | $conf->set('general.timezone', $tz); | ||
1851 | $login = $_POST['setlogin']; | ||
1852 | $conf->set('credentials.login', $login); | ||
1853 | $salt = sha1(uniqid('', true) .'_'. mt_rand()); | ||
1854 | $conf->set('credentials.salt', $salt); | ||
1855 | $conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt)); | ||
1856 | if (!empty($_POST['title'])) { | ||
1857 | $conf->set('general.title', escape($_POST['title'])); | ||
1858 | } else { | ||
1859 | $conf->set('general.title', 'Shared bookmarks on '.escape(index_url($_SERVER))); | ||
1860 | } | ||
1861 | $conf->set('translation.language', escape($_POST['language'])); | ||
1862 | $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); | ||
1863 | $conf->set('api.enabled', !empty($_POST['enableApi'])); | ||
1864 | $conf->set( | ||
1865 | 'api.secret', | ||
1866 | generate_api_secret( | ||
1867 | $conf->get('credentials.login'), | ||
1868 | $conf->get('credentials.salt') | ||
1869 | ) | ||
1870 | ); | ||
1871 | try { | ||
1872 | // Everything is ok, let's create config file. | ||
1873 | $conf->write($loginManager->isLoggedIn()); | ||
1874 | } catch (Exception $e) { | ||
1875 | error_log( | ||
1876 | 'ERROR while writing config file after installation.' . PHP_EOL . | ||
1877 | $e->getMessage() | ||
1878 | ); | ||
1879 | |||
1880 | // TODO: do not handle exceptions/errors in JS. | ||
1881 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>'; | ||
1882 | exit; | ||
1883 | } | ||
1884 | |||
1885 | $history = new History($conf->get('resource.history')); | ||
1886 | $bookmarkService = new BookmarkFileService($conf, $history, true); | ||
1887 | if ($bookmarkService->count() === 0) { | ||
1888 | $bookmarkService->initialize(); | ||
1889 | } | ||
1890 | 132 | ||
1891 | echo '<script>alert(' | ||
1892 | .'"Shaarli is now configured. ' | ||
1893 | .'Please enter your login/password and start shaaring your bookmarks!"' | ||
1894 | .');document.location=\'./login\';</script>'; | ||
1895 | exit; | ||
1896 | } | ||
1897 | |||
1898 | $PAGE = new PageBuilder($conf, $_SESSION, null, $sessionManager->generateToken()); | ||
1899 | list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); | ||
1900 | $PAGE->assign('continents', $continents); | ||
1901 | $PAGE->assign('cities', $cities); | ||
1902 | $PAGE->assign('languages', Languages::getAvailableLanguages()); | ||
1903 | $PAGE->renderPage('install'); | ||
1904 | exit; | ||
1905 | } | ||
1906 | |||
1907 | if (!isset($_SESSION['LINKS_PER_PAGE'])) { | ||
1908 | $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20); | ||
1909 | } | ||
1910 | |||
1911 | try { | ||
1912 | $history = new History($conf->get('resource.history')); | ||
1913 | } catch (Exception $e) { | ||
1914 | die($e->getMessage()); | ||
1915 | } | ||
1916 | |||
1917 | $linkDb = new BookmarkFileService($conf, $history, $loginManager->isLoggedIn()); | ||
1918 | |||
1919 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { | ||
1920 | showDailyRSS($linkDb, $conf, $loginManager); | ||
1921 | exit; | ||
1922 | } | ||
1923 | |||
1924 | $containerBuilder = new ContainerBuilder($conf, $sessionManager, $loginManager); | ||
1925 | $container = $containerBuilder->build(); | ||
1926 | $app = new App($container); | ||
1927 | 133 | ||
1928 | // REST API routes | 134 | // REST API routes |
1929 | $app->group('/api/v1', function () { | 135 | $app->group('/api/v1', function () { |
@@ -1942,25 +148,6 @@ $app->group('/api/v1', function () { | |||
1942 | $this->get('/history', '\Shaarli\Api\Controllers\HistoryController:getHistory')->setName('getHistory'); | 148 | $this->get('/history', '\Shaarli\Api\Controllers\HistoryController:getHistory')->setName('getHistory'); |
1943 | })->add('\Shaarli\Api\ApiMiddleware'); | 149 | })->add('\Shaarli\Api\ApiMiddleware'); |
1944 | 150 | ||
1945 | $app->group('', function () { | ||
1946 | $this->get('/login', '\Shaarli\Front\Controller\LoginController:index')->setName('login'); | ||
1947 | })->add('\Shaarli\Front\ShaarliMiddleware'); | ||
1948 | |||
1949 | $response = $app->run(true); | 151 | $response = $app->run(true); |
1950 | 152 | ||
1951 | // Hack to make Slim and Shaarli router work together: | 153 | $app->respond($response); |
1952 | // If a Slim route isn't found and NOT API call, we call renderPage(). | ||
1953 | if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { | ||
1954 | // We use UTF-8 for proper international characters handling. | ||
1955 | header('Content-Type: text/html; charset=utf-8'); | ||
1956 | renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager); | ||
1957 | } else { | ||
1958 | $response = $response | ||
1959 | ->withHeader('Access-Control-Allow-Origin', '*') | ||
1960 | ->withHeader( | ||
1961 | 'Access-Control-Allow-Headers', | ||
1962 | 'X-Requested-With, Content-Type, Accept, Origin, Authorization' | ||
1963 | ) | ||
1964 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); | ||
1965 | $app->respond($response); | ||
1966 | } | ||
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"); | ||
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..f26e6129 100644 --- a/plugins/archiveorg/archiveorg.php +++ b/plugins/archiveorg/archiveorg.php | |||
@@ -17,12 +17,13 @@ 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 | if ($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) { |
23 | continue; | 24 | continue; |
24 | } | 25 | } |
25 | $archive = sprintf($archive_html, $value['url'], t('View on archive.org')); | 26 | $archive = sprintf($archive_html, $value['url'], $path, t('View on archive.org')); |
26 | $value['link_plugin'][] = $archive; | 27 | $value['link_plugin'][] = $archive; |
27 | } | 28 | } |
28 | 29 | ||
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php index 8ae1b479..defb01f7 100644 --- a/plugins/demo_plugin/demo_plugin.php +++ b/plugins/demo_plugin/demo_plugin.php | |||
@@ -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. |
@@ -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( |
@@ -441,9 +441,9 @@ function hook_demo_plugin_delete_link($data) | |||
441 | function hook_demo_plugin_render_feed($data) | 441 | function hook_demo_plugin_render_feed($data) |
442 | { | 442 | { |
443 | foreach ($data['links'] as &$link) { | 443 | foreach ($data['links'] as &$link) { |
444 | if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) { | 444 | if ($data['_PAGE_'] == TemplatePage::FEED_ATOM) { |
445 | $link['description'] .= ' - ATOM Feed' ; | 445 | $link['description'] .= ' - ATOM Feed' ; |
446 | } elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) { | 446 | } elseif ($data['_PAGE_'] == TemplatePage::FEED_RSS) { |
447 | $link['description'] .= ' - RSS Feed'; | 447 | $link['description'] .= ' - RSS Feed'; |
448 | } | 448 | } |
449 | } | 449 | } |
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php index dab75dd5..16edd9a6 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. |
@@ -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/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..3b5dae34 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,8 +41,8 @@ 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 | ||
47 | return $data; | 48 | return $data; |
@@ -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/tests/PluginManagerTest.php b/tests/PluginManagerTest.php index 195d959c..a5d5dbe9 100644 --- a/tests/PluginManagerTest.php +++ b/tests/PluginManagerTest.php | |||
@@ -25,7 +25,7 @@ class PluginManagerTest extends \PHPUnit\Framework\TestCase | |||
25 | */ | 25 | */ |
26 | protected $pluginManager; | 26 | protected $pluginManager; |
27 | 27 | ||
28 | public function setUp() | 28 | public function setUp(): void |
29 | { | 29 | { |
30 | $conf = new ConfigManager(''); | 30 | $conf = new ConfigManager(''); |
31 | $this->pluginManager = new PluginManager($conf); | 31 | $this->pluginManager = new PluginManager($conf); |
@@ -33,10 +33,8 @@ class PluginManagerTest extends \PHPUnit\Framework\TestCase | |||
33 | 33 | ||
34 | /** | 34 | /** |
35 | * Test plugin loading and hook execution. | 35 | * Test plugin loading and hook execution. |
36 | * | ||
37 | * @return void | ||
38 | */ | 36 | */ |
39 | public function testPlugin() | 37 | public function testPlugin(): void |
40 | { | 38 | { |
41 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | 39 | PluginManager::$PLUGINS_PATH = self::$pluginPath; |
42 | $this->pluginManager->load(array(self::$pluginName)); | 40 | $this->pluginManager->load(array(self::$pluginName)); |
@@ -57,9 +55,28 @@ class PluginManagerTest extends \PHPUnit\Framework\TestCase | |||
57 | } | 55 | } |
58 | 56 | ||
59 | /** | 57 | /** |
58 | * Test plugin loading and hook execution with an error: raise an incompatibility error. | ||
59 | */ | ||
60 | public function testPluginWithPhpError(): void | ||
61 | { | ||
62 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | ||
63 | $this->pluginManager->load(array(self::$pluginName)); | ||
64 | |||
65 | $this->assertTrue(function_exists('hook_test_error')); | ||
66 | |||
67 | $data = []; | ||
68 | $this->pluginManager->executeHooks('error', $data); | ||
69 | |||
70 | $this->assertSame( | ||
71 | 'test [plugin incompatibility]: Class \'Unknown\' not found', | ||
72 | $this->pluginManager->getErrors()[0] | ||
73 | ); | ||
74 | } | ||
75 | |||
76 | /** | ||
60 | * Test missing plugin loading. | 77 | * Test missing plugin loading. |
61 | */ | 78 | */ |
62 | public function testPluginNotFound() | 79 | public function testPluginNotFound(): void |
63 | { | 80 | { |
64 | $this->pluginManager->load(array()); | 81 | $this->pluginManager->load(array()); |
65 | $this->pluginManager->load(array('nope', 'renope')); | 82 | $this->pluginManager->load(array('nope', 'renope')); |
@@ -69,7 +86,7 @@ class PluginManagerTest extends \PHPUnit\Framework\TestCase | |||
69 | /** | 86 | /** |
70 | * Test plugin metadata loading. | 87 | * Test plugin metadata loading. |
71 | */ | 88 | */ |
72 | public function testGetPluginsMeta() | 89 | public function testGetPluginsMeta(): void |
73 | { | 90 | { |
74 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | 91 | PluginManager::$PLUGINS_PATH = self::$pluginPath; |
75 | $this->pluginManager->load(array(self::$pluginName)); | 92 | $this->pluginManager->load(array(self::$pluginName)); |
diff --git a/tests/api/controllers/links/GetLinkIdTest.php b/tests/api/controllers/links/GetLinkIdTest.php index c26411ac..8bb81dc8 100644 --- a/tests/api/controllers/links/GetLinkIdTest.php +++ b/tests/api/controllers/links/GetLinkIdTest.php | |||
@@ -102,7 +102,7 @@ class GetLinkIdTest extends \PHPUnit\Framework\TestCase | |||
102 | $this->assertEquals($id, $data['id']); | 102 | $this->assertEquals($id, $data['id']); |
103 | 103 | ||
104 | // Check link elements | 104 | // Check link elements |
105 | $this->assertEquals('http://domain.tld/?WDWyig', $data['url']); | 105 | $this->assertEquals('http://domain.tld/shaare/WDWyig', $data['url']); |
106 | $this->assertEquals('WDWyig', $data['shorturl']); | 106 | $this->assertEquals('WDWyig', $data['shorturl']); |
107 | $this->assertEquals('Link title: @website', $data['title']); | 107 | $this->assertEquals('Link title: @website', $data['title']); |
108 | $this->assertEquals( | 108 | $this->assertEquals( |
diff --git a/tests/api/controllers/links/GetLinksTest.php b/tests/api/controllers/links/GetLinksTest.php index 4e2d55ac..d02e6fad 100644 --- a/tests/api/controllers/links/GetLinksTest.php +++ b/tests/api/controllers/links/GetLinksTest.php | |||
@@ -109,7 +109,7 @@ class GetLinksTest extends \PHPUnit\Framework\TestCase | |||
109 | 109 | ||
110 | // Check first element fields | 110 | // Check first element fields |
111 | $first = $data[2]; | 111 | $first = $data[2]; |
112 | $this->assertEquals('http://domain.tld/?WDWyig', $first['url']); | 112 | $this->assertEquals('http://domain.tld/shaare/WDWyig', $first['url']); |
113 | $this->assertEquals('WDWyig', $first['shorturl']); | 113 | $this->assertEquals('WDWyig', $first['shorturl']); |
114 | $this->assertEquals('Link title: @website', $first['title']); | 114 | $this->assertEquals('Link title: @website', $first['title']); |
115 | $this->assertEquals( | 115 | $this->assertEquals( |
diff --git a/tests/api/controllers/links/PostLinkTest.php b/tests/api/controllers/links/PostLinkTest.php index b2dd09eb..4e791a04 100644 --- a/tests/api/controllers/links/PostLinkTest.php +++ b/tests/api/controllers/links/PostLinkTest.php | |||
@@ -131,8 +131,8 @@ class PostLinkTest extends TestCase | |||
131 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | 131 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); |
132 | $this->assertEquals(43, $data['id']); | 132 | $this->assertEquals(43, $data['id']); |
133 | $this->assertRegExp('/[\w_-]{6}/', $data['shorturl']); | 133 | $this->assertRegExp('/[\w_-]{6}/', $data['shorturl']); |
134 | $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']); | 134 | $this->assertEquals('http://domain.tld/shaare/' . $data['shorturl'], $data['url']); |
135 | $this->assertEquals('?' . $data['shorturl'], $data['title']); | 135 | $this->assertEquals('/shaare/' . $data['shorturl'], $data['title']); |
136 | $this->assertEquals('', $data['description']); | 136 | $this->assertEquals('', $data['description']); |
137 | $this->assertEquals([], $data['tags']); | 137 | $this->assertEquals([], $data['tags']); |
138 | $this->assertEquals(true, $data['private']); | 138 | $this->assertEquals(true, $data['private']); |
diff --git a/tests/api/controllers/links/PutLinkTest.php b/tests/api/controllers/links/PutLinkTest.php index cb63742e..302cac0f 100644 --- a/tests/api/controllers/links/PutLinkTest.php +++ b/tests/api/controllers/links/PutLinkTest.php | |||
@@ -114,8 +114,8 @@ class PutLinkTest extends \PHPUnit\Framework\TestCase | |||
114 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | 114 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); |
115 | $this->assertEquals($id, $data['id']); | 115 | $this->assertEquals($id, $data['id']); |
116 | $this->assertEquals('WDWyig', $data['shorturl']); | 116 | $this->assertEquals('WDWyig', $data['shorturl']); |
117 | $this->assertEquals('http://domain.tld/?WDWyig', $data['url']); | 117 | $this->assertEquals('http://domain.tld/shaare/WDWyig', $data['url']); |
118 | $this->assertEquals('?WDWyig', $data['title']); | 118 | $this->assertEquals('/shaare/WDWyig', $data['title']); |
119 | $this->assertEquals('', $data['description']); | 119 | $this->assertEquals('', $data['description']); |
120 | $this->assertEquals([], $data['tags']); | 120 | $this->assertEquals([], $data['tags']); |
121 | $this->assertEquals(true, $data['private']); | 121 | $this->assertEquals(true, $data['private']); |
diff --git a/tests/bookmark/BookmarkFileServiceTest.php b/tests/bookmark/BookmarkFileServiceTest.php index 4900d41d..7b1906d3 100644 --- a/tests/bookmark/BookmarkFileServiceTest.php +++ b/tests/bookmark/BookmarkFileServiceTest.php | |||
@@ -200,7 +200,7 @@ class BookmarkFileServiceTest extends TestCase | |||
200 | 200 | ||
201 | $bookmark = $this->privateLinkDB->get(43); | 201 | $bookmark = $this->privateLinkDB->get(43); |
202 | $this->assertEquals(43, $bookmark->getId()); | 202 | $this->assertEquals(43, $bookmark->getId()); |
203 | $this->assertRegExp('/\?[\w\-]{6}/', $bookmark->getUrl()); | 203 | $this->assertRegExp('#/shaare/[\w\-]{6}#', $bookmark->getUrl()); |
204 | $this->assertRegExp('/[\w\-]{6}/', $bookmark->getShortUrl()); | 204 | $this->assertRegExp('/[\w\-]{6}/', $bookmark->getShortUrl()); |
205 | $this->assertEquals($bookmark->getUrl(), $bookmark->getTitle()); | 205 | $this->assertEquals($bookmark->getUrl(), $bookmark->getTitle()); |
206 | $this->assertEmpty($bookmark->getDescription()); | 206 | $this->assertEmpty($bookmark->getDescription()); |
@@ -216,7 +216,7 @@ class BookmarkFileServiceTest extends TestCase | |||
216 | 216 | ||
217 | $bookmark = $this->privateLinkDB->get(43); | 217 | $bookmark = $this->privateLinkDB->get(43); |
218 | $this->assertEquals(43, $bookmark->getId()); | 218 | $this->assertEquals(43, $bookmark->getId()); |
219 | $this->assertRegExp('/\?[\w\-]{6}/', $bookmark->getUrl()); | 219 | $this->assertRegExp('#/shaare/[\w\-]{6}#', $bookmark->getUrl()); |
220 | $this->assertRegExp('/[\w\-]{6}/', $bookmark->getShortUrl()); | 220 | $this->assertRegExp('/[\w\-]{6}/', $bookmark->getShortUrl()); |
221 | $this->assertEquals($bookmark->getUrl(), $bookmark->getTitle()); | 221 | $this->assertEquals($bookmark->getUrl(), $bookmark->getTitle()); |
222 | $this->assertEmpty($bookmark->getDescription()); | 222 | $this->assertEmpty($bookmark->getDescription()); |
@@ -340,7 +340,7 @@ class BookmarkFileServiceTest extends TestCase | |||
340 | 340 | ||
341 | $bookmark = $this->privateLinkDB->get(42); | 341 | $bookmark = $this->privateLinkDB->get(42); |
342 | $this->assertEquals(42, $bookmark->getId()); | 342 | $this->assertEquals(42, $bookmark->getId()); |
343 | $this->assertEquals('?WDWyig', $bookmark->getUrl()); | 343 | $this->assertEquals('/shaare/WDWyig', $bookmark->getUrl()); |
344 | $this->assertEquals('1eYJ1Q', $bookmark->getShortUrl()); | 344 | $this->assertEquals('1eYJ1Q', $bookmark->getShortUrl()); |
345 | $this->assertEquals('Note: I have a big ID but an old date', $bookmark->getTitle()); | 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()); | 346 | $this->assertEquals('Used to test bookmarks reordering.', $bookmark->getDescription()); |
@@ -359,7 +359,7 @@ class BookmarkFileServiceTest extends TestCase | |||
359 | 359 | ||
360 | $bookmark = $this->privateLinkDB->get(42); | 360 | $bookmark = $this->privateLinkDB->get(42); |
361 | $this->assertEquals(42, $bookmark->getId()); | 361 | $this->assertEquals(42, $bookmark->getId()); |
362 | $this->assertEquals('?WDWyig', $bookmark->getUrl()); | 362 | $this->assertEquals('/shaare/WDWyig', $bookmark->getUrl()); |
363 | $this->assertEquals('1eYJ1Q', $bookmark->getShortUrl()); | 363 | $this->assertEquals('1eYJ1Q', $bookmark->getShortUrl()); |
364 | $this->assertEquals('Note: I have a big ID but an old date', $bookmark->getTitle()); | 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()); | 365 | $this->assertEquals('Used to test bookmarks reordering.', $bookmark->getDescription()); |
@@ -816,7 +816,6 @@ class BookmarkFileServiceTest extends TestCase | |||
816 | ); | 816 | ); |
817 | $this->assertEquals( | 817 | $this->assertEquals( |
818 | [ | 818 | [ |
819 | 'web' => 4, | ||
820 | 'cartoon' => 2, | 819 | 'cartoon' => 2, |
821 | 'gnu' => 1, | 820 | 'gnu' => 1, |
822 | 'dev' => 1, | 821 | 'dev' => 1, |
@@ -833,7 +832,6 @@ class BookmarkFileServiceTest extends TestCase | |||
833 | ); | 832 | ); |
834 | $this->assertEquals( | 833 | $this->assertEquals( |
835 | [ | 834 | [ |
836 | 'web' => 1, | ||
837 | 'html' => 1, | 835 | 'html' => 1, |
838 | 'w3c' => 1, | 836 | 'w3c' => 1, |
839 | 'css' => 1, | 837 | 'css' => 1, |
@@ -894,35 +892,35 @@ class BookmarkFileServiceTest extends TestCase | |||
894 | public function testFilterHashValid() | 892 | public function testFilterHashValid() |
895 | { | 893 | { |
896 | $request = smallHash('20150310_114651'); | 894 | $request = smallHash('20150310_114651'); |
897 | $this->assertEquals( | 895 | $this->assertSame( |
898 | 1, | 896 | $request, |
899 | count($this->publicLinkDB->findByHash($request)) | 897 | $this->publicLinkDB->findByHash($request)->getShortUrl() |
900 | ); | 898 | ); |
901 | $request = smallHash('20150310_114633' . 8); | 899 | $request = smallHash('20150310_114633' . 8); |
902 | $this->assertEquals( | 900 | $this->assertSame( |
903 | 1, | 901 | $request, |
904 | count($this->publicLinkDB->findByHash($request)) | 902 | $this->publicLinkDB->findByHash($request)->getShortUrl() |
905 | ); | 903 | ); |
906 | } | 904 | } |
907 | 905 | ||
908 | /** | 906 | /** |
909 | * Test filterHash() with an invalid smallhash. | 907 | * Test filterHash() with an invalid smallhash. |
910 | * | ||
911 | * @expectedException \Shaarli\Bookmark\Exception\BookmarkNotFoundException | ||
912 | */ | 908 | */ |
913 | public function testFilterHashInValid1() | 909 | public function testFilterHashInValid1() |
914 | { | 910 | { |
911 | $this->expectException(BookmarkNotFoundException::class); | ||
912 | |||
915 | $request = 'blabla'; | 913 | $request = 'blabla'; |
916 | $this->publicLinkDB->findByHash($request); | 914 | $this->publicLinkDB->findByHash($request); |
917 | } | 915 | } |
918 | 916 | ||
919 | /** | 917 | /** |
920 | * Test filterHash() with an empty smallhash. | 918 | * Test filterHash() with an empty smallhash. |
921 | * | ||
922 | * @expectedException \Shaarli\Bookmark\Exception\BookmarkNotFoundException | ||
923 | */ | 919 | */ |
924 | public function testFilterHashInValid() | 920 | public function testFilterHashInValid() |
925 | { | 921 | { |
922 | $this->expectException(BookmarkNotFoundException::class); | ||
923 | |||
926 | $this->publicLinkDB->findByHash(''); | 924 | $this->publicLinkDB->findByHash(''); |
927 | } | 925 | } |
928 | 926 | ||
@@ -968,7 +966,6 @@ class BookmarkFileServiceTest extends TestCase | |||
968 | public function testCountLinkPerTagAllWithFilter() | 966 | public function testCountLinkPerTagAllWithFilter() |
969 | { | 967 | { |
970 | $expected = [ | 968 | $expected = [ |
971 | 'gnu' => 2, | ||
972 | 'hashtag' => 2, | 969 | 'hashtag' => 2, |
973 | '-exclude' => 1, | 970 | '-exclude' => 1, |
974 | '.hidden' => 1, | 971 | '.hidden' => 1, |
@@ -991,7 +988,6 @@ class BookmarkFileServiceTest extends TestCase | |||
991 | public function testCountLinkPerTagPublicWithFilter() | 988 | public function testCountLinkPerTagPublicWithFilter() |
992 | { | 989 | { |
993 | $expected = [ | 990 | $expected = [ |
994 | 'gnu' => 2, | ||
995 | 'hashtag' => 2, | 991 | 'hashtag' => 2, |
996 | '-exclude' => 1, | 992 | '-exclude' => 1, |
997 | '.hidden' => 1, | 993 | '.hidden' => 1, |
@@ -1015,7 +1011,6 @@ class BookmarkFileServiceTest extends TestCase | |||
1015 | { | 1011 | { |
1016 | $expected = [ | 1012 | $expected = [ |
1017 | 'cartoon' => 1, | 1013 | 'cartoon' => 1, |
1018 | 'dev' => 1, | ||
1019 | 'tag1' => 1, | 1014 | 'tag1' => 1, |
1020 | 'tag2' => 1, | 1015 | 'tag2' => 1, |
1021 | 'tag3' => 1, | 1016 | 'tag3' => 1, |
diff --git a/tests/bookmark/BookmarkInitializerTest.php b/tests/bookmark/BookmarkInitializerTest.php index d23eb069..3906cc7f 100644 --- a/tests/bookmark/BookmarkInitializerTest.php +++ b/tests/bookmark/BookmarkInitializerTest.php | |||
@@ -3,7 +3,6 @@ | |||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Bookmark; |
4 | 4 | ||
5 | use PHPUnit\Framework\TestCase; | 5 | use PHPUnit\Framework\TestCase; |
6 | use ReferenceLinkDB; | ||
7 | use Shaarli\Config\ConfigManager; | 6 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\History; | 7 | use Shaarli\History; |
9 | 8 | ||
@@ -54,9 +53,9 @@ class BookmarkInitializerTest extends TestCase | |||
54 | } | 53 | } |
55 | 54 | ||
56 | /** | 55 | /** |
57 | * Test initialize() with an empty data store. | 56 | * Test initialize() with a data store containing bookmarks. |
58 | */ | 57 | */ |
59 | public function testInitializeEmptyDataStore() | 58 | public function testInitializeNotEmptyDataStore(): void |
60 | { | 59 | { |
61 | $refDB = new \ReferenceLinkDB(); | 60 | $refDB = new \ReferenceLinkDB(); |
62 | $refDB->write(self::$testDatastore); | 61 | $refDB->write(self::$testDatastore); |
@@ -79,6 +78,8 @@ class BookmarkInitializerTest extends TestCase | |||
79 | ); | 78 | ); |
80 | $this->assertFalse($bookmark->isPrivate()); | 79 | $this->assertFalse($bookmark->isPrivate()); |
81 | 80 | ||
81 | $this->bookmarkService->save(); | ||
82 | |||
82 | // Reload from file | 83 | // Reload from file |
83 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | 84 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); |
84 | $this->assertEquals($refDB->countLinks() + 2, $this->bookmarkService->count()); | 85 | $this->assertEquals($refDB->countLinks() + 2, $this->bookmarkService->count()); |
@@ -97,10 +98,13 @@ class BookmarkInitializerTest extends TestCase | |||
97 | } | 98 | } |
98 | 99 | ||
99 | /** | 100 | /** |
100 | * Test initialize() with a data store containing bookmarks. | 101 | * Test initialize() with an a non existent datastore file . |
101 | */ | 102 | */ |
102 | public function testInitializeNotEmptyDataStore() | 103 | public function testInitializeNonExistentDataStore(): void |
103 | { | 104 | { |
105 | $this->conf->set('resource.datastore', static::$testDatastore . '_empty'); | ||
106 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
107 | |||
104 | $this->initializer->initialize(); | 108 | $this->initializer->initialize(); |
105 | 109 | ||
106 | $this->assertEquals(2, $this->bookmarkService->count()); | 110 | $this->assertEquals(2, $this->bookmarkService->count()); |
diff --git a/tests/bookmark/BookmarkTest.php b/tests/bookmark/BookmarkTest.php index 9a3bbbfc..4b6a3c07 100644 --- a/tests/bookmark/BookmarkTest.php +++ b/tests/bookmark/BookmarkTest.php | |||
@@ -124,8 +124,8 @@ class BookmarkTest extends TestCase | |||
124 | $this->assertEquals(1, $bookmark->getId()); | 124 | $this->assertEquals(1, $bookmark->getId()); |
125 | $this->assertEquals('abc', $bookmark->getShortUrl()); | 125 | $this->assertEquals('abc', $bookmark->getShortUrl()); |
126 | $this->assertEquals($date, $bookmark->getCreated()); | 126 | $this->assertEquals($date, $bookmark->getCreated()); |
127 | $this->assertEquals('?abc', $bookmark->getUrl()); | 127 | $this->assertEquals('/shaare/abc', $bookmark->getUrl()); |
128 | $this->assertEquals('?abc', $bookmark->getTitle()); | 128 | $this->assertEquals('/shaare/abc', $bookmark->getTitle()); |
129 | $this->assertEquals('', $bookmark->getDescription()); | 129 | $this->assertEquals('', $bookmark->getDescription()); |
130 | $this->assertEquals([], $bookmark->getTags()); | 130 | $this->assertEquals([], $bookmark->getTags()); |
131 | $this->assertEquals('', $bookmark->getTagsString()); | 131 | $this->assertEquals('', $bookmark->getTagsString()); |
diff --git a/tests/bookmark/LinkUtilsTest.php b/tests/bookmark/LinkUtilsTest.php index 591976f2..7d4a7b89 100644 --- a/tests/bookmark/LinkUtilsTest.php +++ b/tests/bookmark/LinkUtilsTest.php | |||
@@ -3,8 +3,6 @@ | |||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Bookmark; |
4 | 4 | ||
5 | use PHPUnit\Framework\TestCase; | 5 | use PHPUnit\Framework\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 | ||
@@ -491,7 +489,7 @@ class LinkUtilsTest extends TestCase | |||
491 | */ | 489 | */ |
492 | private function getHashtagLink($hashtag, $index = '') | 490 | private function getHashtagLink($hashtag, $index = '') |
493 | { | 491 | { |
494 | $hashtagLink = '<a href="' . $index . '?addtag=$1" title="Hashtag $1">#$1</a>'; | 492 | $hashtagLink = '<a href="' . $index . './add-tag/$1" title="Hashtag $1">#$1</a>'; |
495 | return str_replace('$1', $hashtag, $hashtagLink); | 493 | return str_replace('$1', $hashtag, $hashtagLink); |
496 | } | 494 | } |
497 | } | 495 | } |
diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0afbcba6..d4ddedd5 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php | |||
@@ -18,7 +18,14 @@ require_once 'application/bookmark/LinkUtils.php'; | |||
18 | require_once 'application/Utils.php'; | 18 | require_once 'application/Utils.php'; |
19 | require_once 'application/http/UrlUtils.php'; | 19 | require_once 'application/http/UrlUtils.php'; |
20 | require_once 'application/http/HttpUtils.php'; | 20 | require_once 'application/http/HttpUtils.php'; |
21 | require_once 'application/feed/Cache.php'; | 21 | require_once 'tests/container/ShaarliTestContainer.php'; |
22 | require_once 'tests/utils/ReferenceLinkDB.php'; | 22 | require_once 'tests/front/controller/visitor/FrontControllerMockHelper.php'; |
23 | require_once 'tests/utils/ReferenceHistory.php'; | 23 | require_once 'tests/front/controller/admin/FrontAdminControllerMockHelper.php'; |
24 | require_once 'tests/updater/DummyUpdater.php'; | ||
24 | require_once 'tests/utils/FakeBookmarkService.php'; | 25 | require_once 'tests/utils/FakeBookmarkService.php'; |
26 | require_once 'tests/utils/FakeConfigManager.php'; | ||
27 | require_once 'tests/utils/ReferenceHistory.php'; | ||
28 | require_once 'tests/utils/ReferenceLinkDB.php'; | ||
29 | require_once 'tests/utils/ReferenceSessionIdHashes.php'; | ||
30 | |||
31 | \ReferenceSessionIdHashes::genAllHashes(); | ||
diff --git a/tests/config/ConfigPluginTest.php b/tests/config/ConfigPluginTest.php index d7a70e68..b2cc0045 100644 --- a/tests/config/ConfigPluginTest.php +++ b/tests/config/ConfigPluginTest.php | |||
@@ -2,6 +2,7 @@ | |||
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 | ||
@@ -17,23 +18,30 @@ 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 | /** |
diff --git a/tests/container/ContainerBuilderTest.php b/tests/container/ContainerBuilderTest.php index 9b97ed6d..c08010ae 100644 --- a/tests/container/ContainerBuilderTest.php +++ b/tests/container/ContainerBuilderTest.php | |||
@@ -7,10 +7,21 @@ namespace Shaarli\Container; | |||
7 | use PHPUnit\Framework\TestCase; | 7 | use PHPUnit\Framework\TestCase; |
8 | use Shaarli\Bookmark\BookmarkServiceInterface; | 8 | use Shaarli\Bookmark\BookmarkServiceInterface; |
9 | use Shaarli\Config\ConfigManager; | 9 | use Shaarli\Config\ConfigManager; |
10 | use Shaarli\Feed\FeedBuilder; | ||
11 | use Shaarli\Formatter\FormatterFactory; | ||
12 | use Shaarli\Front\Controller\Visitor\ErrorController; | ||
10 | use Shaarli\History; | 13 | use Shaarli\History; |
14 | use Shaarli\Http\HttpAccess; | ||
15 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
16 | use Shaarli\Plugin\PluginManager; | ||
11 | use Shaarli\Render\PageBuilder; | 17 | use Shaarli\Render\PageBuilder; |
18 | use Shaarli\Render\PageCacheManager; | ||
19 | use Shaarli\Security\CookieManager; | ||
12 | use Shaarli\Security\LoginManager; | 20 | use Shaarli\Security\LoginManager; |
13 | use Shaarli\Security\SessionManager; | 21 | use Shaarli\Security\SessionManager; |
22 | use Shaarli\Thumbnailer; | ||
23 | use Shaarli\Updater\Updater; | ||
24 | use Slim\Http\Environment; | ||
14 | 25 | ||
15 | class ContainerBuilderTest extends TestCase | 26 | class ContainerBuilderTest extends TestCase |
16 | { | 27 | { |
@@ -26,24 +37,50 @@ class ContainerBuilderTest extends TestCase | |||
26 | /** @var ContainerBuilder */ | 37 | /** @var ContainerBuilder */ |
27 | protected $containerBuilder; | 38 | protected $containerBuilder; |
28 | 39 | ||
40 | /** @var CookieManager */ | ||
41 | protected $cookieManager; | ||
42 | |||
29 | public function setUp(): void | 43 | public function setUp(): void |
30 | { | 44 | { |
31 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 45 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
32 | $this->sessionManager = $this->createMock(SessionManager::class); | 46 | $this->sessionManager = $this->createMock(SessionManager::class); |
47 | $this->cookieManager = $this->createMock(CookieManager::class); | ||
48 | |||
33 | $this->loginManager = $this->createMock(LoginManager::class); | 49 | $this->loginManager = $this->createMock(LoginManager::class); |
50 | $this->loginManager->method('isLoggedIn')->willReturn(true); | ||
34 | 51 | ||
35 | $this->containerBuilder = new ContainerBuilder($this->conf, $this->sessionManager, $this->loginManager); | 52 | $this->containerBuilder = new ContainerBuilder( |
53 | $this->conf, | ||
54 | $this->sessionManager, | ||
55 | $this->cookieManager, | ||
56 | $this->loginManager | ||
57 | ); | ||
36 | } | 58 | } |
37 | 59 | ||
38 | public function testBuildContainer(): void | 60 | public function testBuildContainer(): void |
39 | { | 61 | { |
40 | $container = $this->containerBuilder->build(); | 62 | $container = $this->containerBuilder->build(); |
41 | 63 | ||
64 | static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService); | ||
65 | static::assertInstanceOf(CookieManager::class, $container->cookieManager); | ||
42 | static::assertInstanceOf(ConfigManager::class, $container->conf); | 66 | static::assertInstanceOf(ConfigManager::class, $container->conf); |
43 | static::assertInstanceOf(SessionManager::class, $container->sessionManager); | 67 | static::assertInstanceOf(ErrorController::class, $container->errorHandler); |
44 | static::assertInstanceOf(LoginManager::class, $container->loginManager); | 68 | static::assertInstanceOf(Environment::class, $container->environment); |
69 | static::assertInstanceOf(FeedBuilder::class, $container->feedBuilder); | ||
70 | static::assertInstanceOf(FormatterFactory::class, $container->formatterFactory); | ||
45 | static::assertInstanceOf(History::class, $container->history); | 71 | static::assertInstanceOf(History::class, $container->history); |
46 | static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService); | 72 | static::assertInstanceOf(HttpAccess::class, $container->httpAccess); |
73 | static::assertInstanceOf(LoginManager::class, $container->loginManager); | ||
74 | static::assertInstanceOf(NetscapeBookmarkUtils::class, $container->netscapeBookmarkUtils); | ||
47 | static::assertInstanceOf(PageBuilder::class, $container->pageBuilder); | 75 | static::assertInstanceOf(PageBuilder::class, $container->pageBuilder); |
76 | static::assertInstanceOf(PageCacheManager::class, $container->pageCacheManager); | ||
77 | static::assertInstanceOf(ErrorController::class, $container->phpErrorHandler); | ||
78 | static::assertInstanceOf(PluginManager::class, $container->pluginManager); | ||
79 | static::assertInstanceOf(SessionManager::class, $container->sessionManager); | ||
80 | static::assertInstanceOf(Thumbnailer::class, $container->thumbnailer); | ||
81 | static::assertInstanceOf(Updater::class, $container->updater); | ||
82 | |||
83 | // Set by the middleware | ||
84 | static::assertNull($container->basePath); | ||
48 | } | 85 | } |
49 | } | 86 | } |
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 363028a2..2e716432 100644 --- a/tests/feed/CachedPageTest.php +++ b/tests/feed/CachedPageTest.php | |||
@@ -11,7 +11,7 @@ class CachedPageTest extends \PHPUnit\Framework\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 | /** |
@@ -42,8 +42,8 @@ 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 | $this->addToAssertionCount(1); |
48 | } | 48 | } |
49 | 49 | ||
diff --git a/tests/feed/FeedBuilderTest.php b/tests/feed/FeedBuilderTest.php index 54671891..5c2aaedb 100644 --- a/tests/feed/FeedBuilderTest.php +++ b/tests/feed/FeedBuilderTest.php | |||
@@ -65,23 +65,6 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
65 | } | 65 | } |
66 | 66 | ||
67 | /** | 67 | /** |
68 | * Test GetTypeLanguage(). | ||
69 | */ | ||
70 | public function testGetTypeLanguage() | ||
71 | { | ||
72 | $feedBuilder = new FeedBuilder(null, self::$formatter, FeedBuilder::$FEED_ATOM, null, null, false); | ||
73 | $feedBuilder->setLocale(self::$LOCALE); | ||
74 | $this->assertEquals(self::$ATOM_LANGUAGUE, $feedBuilder->getTypeLanguage()); | ||
75 | $feedBuilder = new FeedBuilder(null, self::$formatter, FeedBuilder::$FEED_RSS, null, null, false); | ||
76 | $feedBuilder->setLocale(self::$LOCALE); | ||
77 | $this->assertEquals(self::$RSS_LANGUAGE, $feedBuilder->getTypeLanguage()); | ||
78 | $feedBuilder = new FeedBuilder(null, self::$formatter, FeedBuilder::$FEED_ATOM, null, null, false); | ||
79 | $this->assertEquals('en', $feedBuilder->getTypeLanguage()); | ||
80 | $feedBuilder = new FeedBuilder(null, self::$formatter, FeedBuilder::$FEED_RSS, null, null, false); | ||
81 | $this->assertEquals('en-en', $feedBuilder->getTypeLanguage()); | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Test buildData with RSS feed. | 68 | * Test buildData with RSS feed. |
86 | */ | 69 | */ |
87 | public function testRSSBuildData() | 70 | public function testRSSBuildData() |
@@ -89,13 +72,11 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
89 | $feedBuilder = new FeedBuilder( | 72 | $feedBuilder = new FeedBuilder( |
90 | self::$bookmarkService, | 73 | self::$bookmarkService, |
91 | self::$formatter, | 74 | self::$formatter, |
92 | FeedBuilder::$FEED_RSS, | 75 | static::$serverInfo, |
93 | self::$serverInfo, | ||
94 | null, | ||
95 | false | 76 | false |
96 | ); | 77 | ); |
97 | $feedBuilder->setLocale(self::$LOCALE); | 78 | $feedBuilder->setLocale(self::$LOCALE); |
98 | $data = $feedBuilder->buildData(); | 79 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_RSS, null); |
99 | // Test headers (RSS) | 80 | // Test headers (RSS) |
100 | $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); | 81 | $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); |
101 | $this->assertRegExp('/Wed, 03 Aug 2016 09:30:33 \+\d{4}/', $data['last_update']); | 82 | $this->assertRegExp('/Wed, 03 Aug 2016 09:30:33 \+\d{4}/', $data['last_update']); |
@@ -109,15 +90,15 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
109 | $link = $data['links'][array_keys($data['links'])[2]]; | 90 | $link = $data['links'][array_keys($data['links'])[2]]; |
110 | $this->assertEquals(41, $link['id']); | 91 | $this->assertEquals(41, $link['id']); |
111 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); | 92 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); |
112 | $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); | 93 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['guid']); |
113 | $this->assertEquals('http://host.tld/?WDWyig', $link['url']); | 94 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['url']); |
114 | $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']); | 95 | $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']); |
115 | $pub = DateTime::createFromFormat(DateTime::RSS, $link['pub_iso_date']); | 96 | $pub = DateTime::createFromFormat(DateTime::RSS, $link['pub_iso_date']); |
116 | $up = DateTime::createFromFormat(DateTime::ATOM, $link['up_iso_date']); | 97 | $up = DateTime::createFromFormat(DateTime::ATOM, $link['up_iso_date']); |
117 | $this->assertEquals($pub, $up); | 98 | $this->assertEquals($pub, $up); |
118 | $this->assertContains('Stallman has a beard', $link['description']); | 99 | $this->assertContains('Stallman has a beard', $link['description']); |
119 | $this->assertContains('Permalink', $link['description']); | 100 | $this->assertContains('Permalink', $link['description']); |
120 | $this->assertContains('http://host.tld/?WDWyig', $link['description']); | 101 | $this->assertContains('http://host.tld/shaare/WDWyig', $link['description']); |
121 | $this->assertEquals(1, count($link['taglist'])); | 102 | $this->assertEquals(1, count($link['taglist'])); |
122 | $this->assertEquals('sTuff', $link['taglist'][0]); | 103 | $this->assertEquals('sTuff', $link['taglist'][0]); |
123 | 104 | ||
@@ -140,13 +121,11 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
140 | $feedBuilder = new FeedBuilder( | 121 | $feedBuilder = new FeedBuilder( |
141 | self::$bookmarkService, | 122 | self::$bookmarkService, |
142 | self::$formatter, | 123 | self::$formatter, |
143 | FeedBuilder::$FEED_ATOM, | 124 | static::$serverInfo, |
144 | self::$serverInfo, | ||
145 | null, | ||
146 | false | 125 | false |
147 | ); | 126 | ); |
148 | $feedBuilder->setLocale(self::$LOCALE); | 127 | $feedBuilder->setLocale(self::$LOCALE); |
149 | $data = $feedBuilder->buildData(); | 128 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
150 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 129 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
151 | $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); | 130 | $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); |
152 | $link = $data['links'][array_keys($data['links'])[2]]; | 131 | $link = $data['links'][array_keys($data['links'])[2]]; |
@@ -166,13 +145,11 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
166 | $feedBuilder = new FeedBuilder( | 145 | $feedBuilder = new FeedBuilder( |
167 | self::$bookmarkService, | 146 | self::$bookmarkService, |
168 | self::$formatter, | 147 | self::$formatter, |
169 | FeedBuilder::$FEED_ATOM, | 148 | static::$serverInfo, |
170 | self::$serverInfo, | ||
171 | $criteria, | ||
172 | false | 149 | false |
173 | ); | 150 | ); |
174 | $feedBuilder->setLocale(self::$LOCALE); | 151 | $feedBuilder->setLocale(self::$LOCALE); |
175 | $data = $feedBuilder->buildData(); | 152 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, $criteria); |
176 | $this->assertEquals(1, count($data['links'])); | 153 | $this->assertEquals(1, count($data['links'])); |
177 | $link = array_shift($data['links']); | 154 | $link = array_shift($data['links']); |
178 | $this->assertEquals(41, $link['id']); | 155 | $this->assertEquals(41, $link['id']); |
@@ -190,13 +167,11 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
190 | $feedBuilder = new FeedBuilder( | 167 | $feedBuilder = new FeedBuilder( |
191 | self::$bookmarkService, | 168 | self::$bookmarkService, |
192 | self::$formatter, | 169 | self::$formatter, |
193 | FeedBuilder::$FEED_ATOM, | 170 | static::$serverInfo, |
194 | self::$serverInfo, | ||
195 | $criteria, | ||
196 | false | 171 | false |
197 | ); | 172 | ); |
198 | $feedBuilder->setLocale(self::$LOCALE); | 173 | $feedBuilder->setLocale(self::$LOCALE); |
199 | $data = $feedBuilder->buildData(); | 174 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, $criteria); |
200 | $this->assertEquals(3, count($data['links'])); | 175 | $this->assertEquals(3, count($data['links'])); |
201 | $link = $data['links'][array_keys($data['links'])[2]]; | 176 | $link = $data['links'][array_keys($data['links'])[2]]; |
202 | $this->assertEquals(41, $link['id']); | 177 | $this->assertEquals(41, $link['id']); |
@@ -211,29 +186,27 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
211 | $feedBuilder = new FeedBuilder( | 186 | $feedBuilder = new FeedBuilder( |
212 | self::$bookmarkService, | 187 | self::$bookmarkService, |
213 | self::$formatter, | 188 | self::$formatter, |
214 | FeedBuilder::$FEED_ATOM, | 189 | static::$serverInfo, |
215 | self::$serverInfo, | ||
216 | null, | ||
217 | false | 190 | false |
218 | ); | 191 | ); |
219 | $feedBuilder->setLocale(self::$LOCALE); | 192 | $feedBuilder->setLocale(self::$LOCALE); |
220 | $feedBuilder->setUsePermalinks(true); | 193 | $feedBuilder->setUsePermalinks(true); |
221 | $data = $feedBuilder->buildData(); | 194 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
222 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 195 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
223 | $this->assertTrue($data['usepermalinks']); | 196 | $this->assertTrue($data['usepermalinks']); |
224 | // First link is a permalink | 197 | // First link is a permalink |
225 | $link = $data['links'][array_keys($data['links'])[2]]; | 198 | $link = $data['links'][array_keys($data['links'])[2]]; |
226 | $this->assertEquals(41, $link['id']); | 199 | $this->assertEquals(41, $link['id']); |
227 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); | 200 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); |
228 | $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); | 201 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['guid']); |
229 | $this->assertEquals('http://host.tld/?WDWyig', $link['url']); | 202 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['url']); |
230 | $this->assertContains('Direct link', $link['description']); | 203 | $this->assertContains('Direct link', $link['description']); |
231 | $this->assertContains('http://host.tld/?WDWyig', $link['description']); | 204 | $this->assertContains('http://host.tld/shaare/WDWyig', $link['description']); |
232 | // Second link is a direct link | 205 | // Second link is a direct link |
233 | $link = $data['links'][array_keys($data['links'])[3]]; | 206 | $link = $data['links'][array_keys($data['links'])[3]]; |
234 | $this->assertEquals(8, $link['id']); | 207 | $this->assertEquals(8, $link['id']); |
235 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114633'), $link['created']); | 208 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114633'), $link['created']); |
236 | $this->assertEquals('http://host.tld/?RttfEw', $link['guid']); | 209 | $this->assertEquals('http://host.tld/shaare/RttfEw', $link['guid']); |
237 | $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); | 210 | $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); |
238 | $this->assertContains('Direct link', $link['description']); | 211 | $this->assertContains('Direct link', $link['description']); |
239 | $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); | 212 | $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); |
@@ -247,14 +220,12 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
247 | $feedBuilder = new FeedBuilder( | 220 | $feedBuilder = new FeedBuilder( |
248 | self::$bookmarkService, | 221 | self::$bookmarkService, |
249 | self::$formatter, | 222 | self::$formatter, |
250 | FeedBuilder::$FEED_ATOM, | 223 | static::$serverInfo, |
251 | self::$serverInfo, | ||
252 | null, | ||
253 | false | 224 | false |
254 | ); | 225 | ); |
255 | $feedBuilder->setLocale(self::$LOCALE); | 226 | $feedBuilder->setLocale(self::$LOCALE); |
256 | $feedBuilder->setHideDates(true); | 227 | $feedBuilder->setHideDates(true); |
257 | $data = $feedBuilder->buildData(); | 228 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
258 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 229 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
259 | $this->assertFalse($data['show_dates']); | 230 | $this->assertFalse($data['show_dates']); |
260 | 231 | ||
@@ -262,14 +233,12 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
262 | $feedBuilder = new FeedBuilder( | 233 | $feedBuilder = new FeedBuilder( |
263 | self::$bookmarkService, | 234 | self::$bookmarkService, |
264 | self::$formatter, | 235 | self::$formatter, |
265 | FeedBuilder::$FEED_ATOM, | 236 | static::$serverInfo, |
266 | self::$serverInfo, | ||
267 | null, | ||
268 | true | 237 | true |
269 | ); | 238 | ); |
270 | $feedBuilder->setLocale(self::$LOCALE); | 239 | $feedBuilder->setLocale(self::$LOCALE); |
271 | $feedBuilder->setHideDates(true); | 240 | $feedBuilder->setHideDates(true); |
272 | $data = $feedBuilder->buildData(); | 241 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
273 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 242 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
274 | $this->assertTrue($data['show_dates']); | 243 | $this->assertTrue($data['show_dates']); |
275 | } | 244 | } |
@@ -289,13 +258,11 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
289 | $feedBuilder = new FeedBuilder( | 258 | $feedBuilder = new FeedBuilder( |
290 | self::$bookmarkService, | 259 | self::$bookmarkService, |
291 | self::$formatter, | 260 | self::$formatter, |
292 | FeedBuilder::$FEED_ATOM, | ||
293 | $serverInfo, | 261 | $serverInfo, |
294 | null, | ||
295 | false | 262 | false |
296 | ); | 263 | ); |
297 | $feedBuilder->setLocale(self::$LOCALE); | 264 | $feedBuilder->setLocale(self::$LOCALE); |
298 | $data = $feedBuilder->buildData(); | 265 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
299 | 266 | ||
300 | $this->assertEquals( | 267 | $this->assertEquals( |
301 | 'http://host.tld:8080/~user/shaarli/index.php?do=feed', | 268 | 'http://host.tld:8080/~user/shaarli/index.php?do=feed', |
@@ -304,8 +271,8 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
304 | 271 | ||
305 | // Test first link (note link) | 272 | // Test first link (note link) |
306 | $link = $data['links'][array_keys($data['links'])[2]]; | 273 | $link = $data['links'][array_keys($data['links'])[2]]; |
307 | $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['guid']); | 274 | $this->assertEquals('http://host.tld:8080/~user/shaarli/shaare/WDWyig', $link['guid']); |
308 | $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['url']); | 275 | $this->assertEquals('http://host.tld:8080/~user/shaarli/shaare/WDWyig', $link['url']); |
309 | $this->assertContains('http://host.tld:8080/~user/shaarli/?addtag=hashtag', $link['description']); | 276 | $this->assertContains('http://host.tld:8080/~user/shaarli/./add-tag/hashtag', $link['description']); |
310 | } | 277 | } |
311 | } | 278 | } |
diff --git a/tests/formatter/BookmarkDefaultFormatterTest.php b/tests/formatter/BookmarkDefaultFormatterTest.php index 382a560e..cf48b00b 100644 --- a/tests/formatter/BookmarkDefaultFormatterTest.php +++ b/tests/formatter/BookmarkDefaultFormatterTest.php | |||
@@ -123,7 +123,7 @@ class BookmarkDefaultFormatterTest extends TestCase | |||
123 | $description[0] = 'This a <strong>description</strong><br />'; | 123 | $description[0] = 'This a <strong>description</strong><br />'; |
124 | $url = 'https://sub.domain.tld?query=here&for=real#hash'; | 124 | $url = 'https://sub.domain.tld?query=here&for=real#hash'; |
125 | $description[1] = 'text <a href="'. $url .'">'. $url .'</a> more text<br />'; | 125 | $description[1] = 'text <a href="'. $url .'">'. $url .'</a> more text<br />'; |
126 | $description[2] = 'Also, there is an <a href="?addtag=hashtag" '. | 126 | $description[2] = 'Also, there is an <a href="./add-tag/hashtag" '. |
127 | 'title="Hashtag hashtag">#hashtag</a> added<br />'; | 127 | 'title="Hashtag hashtag">#hashtag</a> added<br />'; |
128 | $description[3] = ' A N D KEEP '. | 128 | $description[3] = ' A N D KEEP '. |
129 | 'SPACES ! <br />'; | 129 | 'SPACES ! <br />'; |
@@ -148,7 +148,7 @@ class BookmarkDefaultFormatterTest extends TestCase | |||
148 | $this->assertEquals($root . $short, $link['url']); | 148 | $this->assertEquals($root . $short, $link['url']); |
149 | $this->assertEquals($root . $short, $link['real_url']); | 149 | $this->assertEquals($root . $short, $link['real_url']); |
150 | $this->assertEquals( | 150 | $this->assertEquals( |
151 | 'Text <a href="'. $root .'?addtag=hashtag" title="Hashtag hashtag">'. | 151 | 'Text <a href="'. $root .'./add-tag/hashtag" title="Hashtag hashtag">'. |
152 | '#hashtag</a> more text', | 152 | '#hashtag</a> more text', |
153 | $link['description'] | 153 | $link['description'] |
154 | ); | 154 | ); |
diff --git a/tests/formatter/BookmarkMarkdownFormatterTest.php b/tests/formatter/BookmarkMarkdownFormatterTest.php index f1f12c04..3e72d1ee 100644 --- a/tests/formatter/BookmarkMarkdownFormatterTest.php +++ b/tests/formatter/BookmarkMarkdownFormatterTest.php | |||
@@ -125,7 +125,7 @@ class BookmarkMarkdownFormatterTest extends TestCase | |||
125 | $description .= 'This a <strong>description</strong><br />'. PHP_EOL; | 125 | $description .= 'This a <strong>description</strong><br />'. PHP_EOL; |
126 | $url = 'https://sub.domain.tld?query=here&for=real#hash'; | 126 | $url = 'https://sub.domain.tld?query=here&for=real#hash'; |
127 | $description .= 'text <a href="'. $url .'">'. $url .'</a> more text<br />'. PHP_EOL; | 127 | $description .= 'text <a href="'. $url .'">'. $url .'</a> more text<br />'. PHP_EOL; |
128 | $description .= 'Also, there is an <a href="?addtag=hashtag">#hashtag</a> added<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 ! '; | 129 | $description .= 'A N D KEEP SPACES ! '; |
130 | $description .= '</p></div>'; | 130 | $description .= '</p></div>'; |
131 | 131 | ||
@@ -146,7 +146,7 @@ class BookmarkMarkdownFormatterTest extends TestCase | |||
146 | $this->formatter->addContextData('index_url', $root = 'https://domain.tld/hithere/'); | 146 | $this->formatter->addContextData('index_url', $root = 'https://domain.tld/hithere/'); |
147 | 147 | ||
148 | $description = '<div class="markdown"><p>'; | 148 | $description = '<div class="markdown"><p>'; |
149 | $description .= 'Text <a href="'. $root .'?addtag=hashtag">#hashtag</a> more text'; | 149 | $description .= 'Text <a href="'. $root .'./add-tag/hashtag">#hashtag</a> more text'; |
150 | $description .= '</p></div>'; | 150 | $description .= '</p></div>'; |
151 | 151 | ||
152 | $link = $this->formatter->format($bookmark); | 152 | $link = $this->formatter->format($bookmark); |
diff --git a/tests/front/ShaarliAdminMiddlewareTest.php b/tests/front/ShaarliAdminMiddlewareTest.php new file mode 100644 index 00000000..7451330b --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Container\ShaarliContainer; | ||
10 | use Shaarli\Security\LoginManager; | ||
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 index 80974f37..05aa34a9 100644 --- a/tests/front/ShaarliMiddlewareTest.php +++ b/tests/front/ShaarliMiddlewareTest.php | |||
@@ -8,12 +8,19 @@ use PHPUnit\Framework\TestCase; | |||
8 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Container\ShaarliContainer; | 9 | use Shaarli\Container\ShaarliContainer; |
10 | use Shaarli\Front\Exception\LoginBannedException; | 10 | use Shaarli\Front\Exception\LoginBannedException; |
11 | use Shaarli\Front\Exception\UnauthorizedException; | ||
11 | use Shaarli\Render\PageBuilder; | 12 | use Shaarli\Render\PageBuilder; |
13 | use Shaarli\Render\PageCacheManager; | ||
14 | use Shaarli\Security\LoginManager; | ||
15 | use Shaarli\Updater\Updater; | ||
12 | use Slim\Http\Request; | 16 | use Slim\Http\Request; |
13 | use Slim\Http\Response; | 17 | use Slim\Http\Response; |
18 | use Slim\Http\Uri; | ||
14 | 19 | ||
15 | class ShaarliMiddlewareTest extends TestCase | 20 | class ShaarliMiddlewareTest extends TestCase |
16 | { | 21 | { |
22 | protected const TMP_MOCK_FILE = '.tmp'; | ||
23 | |||
17 | /** @var ShaarliContainer */ | 24 | /** @var ShaarliContainer */ |
18 | protected $container; | 25 | protected $container; |
19 | 26 | ||
@@ -23,12 +30,37 @@ class ShaarliMiddlewareTest extends TestCase | |||
23 | public function setUp(): void | 30 | public function setUp(): void |
24 | { | 31 | { |
25 | $this->container = $this->createMock(ShaarliContainer::class); | 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 | |||
26 | $this->middleware = new ShaarliMiddleware($this->container); | 43 | $this->middleware = new ShaarliMiddleware($this->container); |
27 | } | 44 | } |
28 | 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 | */ | ||
29 | public function testMiddlewareExecution(): void | 54 | public function testMiddlewareExecution(): void |
30 | { | 55 | { |
31 | $request = $this->createMock(Request::class); | 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 | |||
32 | $response = new Response(); | 64 | $response = new Response(); |
33 | $controller = function (Request $request, Response $response): Response { | 65 | $controller = function (Request $request, Response $response): Response { |
34 | return $response->withStatus(418); // I'm a tea pot | 66 | return $response->withStatus(418); // I'm a tea pot |
@@ -41,9 +73,20 @@ class ShaarliMiddlewareTest extends TestCase | |||
41 | static::assertSame(418, $result->getStatusCode()); | 73 | static::assertSame(418, $result->getStatusCode()); |
42 | } | 74 | } |
43 | 75 | ||
44 | public function testMiddlewareExecutionWithException(): void | 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 | ||
45 | { | 81 | { |
46 | $request = $this->createMock(Request::class); | 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 | |||
47 | $response = new Response(); | 90 | $response = new Response(); |
48 | $controller = function (): void { | 91 | $controller = function (): void { |
49 | $exception = new LoginBannedException(); | 92 | $exception = new LoginBannedException(); |
@@ -57,14 +100,122 @@ class ShaarliMiddlewareTest extends TestCase | |||
57 | }); | 100 | }); |
58 | $this->container->pageBuilder = $pageBuilder; | 101 | $this->container->pageBuilder = $pageBuilder; |
59 | 102 | ||
60 | $conf = $this->createMock(ConfigManager::class); | 103 | $this->expectException(LoginBannedException::class); |
61 | $this->container->conf = $conf; | 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 | ; | ||
62 | 214 | ||
63 | /** @var Response $result */ | 215 | /** @var Response $result */ |
64 | $result = $this->middleware->__invoke($request, $response, $controller); | 216 | $result = $this->middleware->__invoke($request, $response, $controller); |
65 | 217 | ||
66 | static::assertInstanceOf(Response::class, $result); | 218 | static::assertInstanceOf(Response::class, $result); |
67 | static::assertSame(401, $result->getStatusCode()); | 219 | static::assertSame(418, $result->getStatusCode()); |
68 | static::assertContains('error', (string) $result->getBody()); | ||
69 | } | 220 | } |
70 | } | 221 | } |
diff --git a/tests/front/controller/LoginControllerTest.php b/tests/front/controller/LoginControllerTest.php deleted file mode 100644 index 8cf8ece7..00000000 --- a/tests/front/controller/LoginControllerTest.php +++ /dev/null | |||
@@ -1,178 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Container\ShaarliContainer; | ||
11 | use Shaarli\Front\Exception\LoginBannedException; | ||
12 | use Shaarli\Plugin\PluginManager; | ||
13 | use Shaarli\Render\PageBuilder; | ||
14 | use Shaarli\Security\LoginManager; | ||
15 | use Slim\Http\Request; | ||
16 | use Slim\Http\Response; | ||
17 | |||
18 | class LoginControllerTest extends TestCase | ||
19 | { | ||
20 | /** @var ShaarliContainer */ | ||
21 | protected $container; | ||
22 | |||
23 | /** @var LoginController */ | ||
24 | protected $controller; | ||
25 | |||
26 | public function setUp(): void | ||
27 | { | ||
28 | $this->container = $this->createMock(ShaarliContainer::class); | ||
29 | $this->controller = new LoginController($this->container); | ||
30 | } | ||
31 | |||
32 | public function testValidControllerInvoke(): void | ||
33 | { | ||
34 | $this->createValidContainerMockSet(); | ||
35 | |||
36 | $request = $this->createMock(Request::class); | ||
37 | $request->expects(static::once())->method('getServerParam')->willReturn('> referer'); | ||
38 | $response = new Response(); | ||
39 | |||
40 | $assignedVariables = []; | ||
41 | $this->container->pageBuilder | ||
42 | ->method('assign') | ||
43 | ->willReturnCallback(function ($key, $value) use (&$assignedVariables) { | ||
44 | $assignedVariables[$key] = $value; | ||
45 | |||
46 | return $this; | ||
47 | }) | ||
48 | ; | ||
49 | |||
50 | $result = $this->controller->index($request, $response); | ||
51 | |||
52 | static::assertInstanceOf(Response::class, $result); | ||
53 | static::assertSame(200, $result->getStatusCode()); | ||
54 | static::assertSame('loginform', (string) $result->getBody()); | ||
55 | |||
56 | static::assertSame('> referer', $assignedVariables['returnurl']); | ||
57 | static::assertSame(true, $assignedVariables['remember_user_default']); | ||
58 | static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']); | ||
59 | } | ||
60 | |||
61 | public function testValidControllerInvokeWithUserName(): void | ||
62 | { | ||
63 | $this->createValidContainerMockSet(); | ||
64 | |||
65 | $request = $this->createMock(Request::class); | ||
66 | $request->expects(static::once())->method('getServerParam')->willReturn('> referer'); | ||
67 | $request->expects(static::exactly(2))->method('getParam')->willReturn('myUser>'); | ||
68 | $response = new Response(); | ||
69 | |||
70 | $assignedVariables = []; | ||
71 | $this->container->pageBuilder | ||
72 | ->method('assign') | ||
73 | ->willReturnCallback(function ($key, $value) use (&$assignedVariables) { | ||
74 | $assignedVariables[$key] = $value; | ||
75 | |||
76 | return $this; | ||
77 | }) | ||
78 | ; | ||
79 | |||
80 | $result = $this->controller->index($request, $response); | ||
81 | |||
82 | static::assertInstanceOf(Response::class, $result); | ||
83 | static::assertSame(200, $result->getStatusCode()); | ||
84 | static::assertSame('loginform', (string) $result->getBody()); | ||
85 | |||
86 | static::assertSame('myUser>', $assignedVariables['username']); | ||
87 | static::assertSame('> referer', $assignedVariables['returnurl']); | ||
88 | static::assertSame(true, $assignedVariables['remember_user_default']); | ||
89 | static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']); | ||
90 | } | ||
91 | |||
92 | public function testLoginControllerWhileLoggedIn(): void | ||
93 | { | ||
94 | $request = $this->createMock(Request::class); | ||
95 | $response = new Response(); | ||
96 | |||
97 | $loginManager = $this->createMock(LoginManager::class); | ||
98 | $loginManager->expects(static::once())->method('isLoggedIn')->willReturn(true); | ||
99 | $this->container->loginManager = $loginManager; | ||
100 | |||
101 | $result = $this->controller->index($request, $response); | ||
102 | |||
103 | static::assertInstanceOf(Response::class, $result); | ||
104 | static::assertSame(302, $result->getStatusCode()); | ||
105 | static::assertSame(['./'], $result->getHeader('Location')); | ||
106 | } | ||
107 | |||
108 | public function testLoginControllerOpenShaarli(): void | ||
109 | { | ||
110 | $this->createValidContainerMockSet(); | ||
111 | |||
112 | $request = $this->createMock(Request::class); | ||
113 | $response = new Response(); | ||
114 | |||
115 | $conf = $this->createMock(ConfigManager::class); | ||
116 | $conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
117 | if ($parameter === 'security.open_shaarli') { | ||
118 | return true; | ||
119 | } | ||
120 | return $default; | ||
121 | }); | ||
122 | $this->container->conf = $conf; | ||
123 | |||
124 | $result = $this->controller->index($request, $response); | ||
125 | |||
126 | static::assertInstanceOf(Response::class, $result); | ||
127 | static::assertSame(302, $result->getStatusCode()); | ||
128 | static::assertSame(['./'], $result->getHeader('Location')); | ||
129 | } | ||
130 | |||
131 | public function testLoginControllerWhileBanned(): void | ||
132 | { | ||
133 | $this->createValidContainerMockSet(); | ||
134 | |||
135 | $request = $this->createMock(Request::class); | ||
136 | $response = new Response(); | ||
137 | |||
138 | $loginManager = $this->createMock(LoginManager::class); | ||
139 | $loginManager->method('isLoggedIn')->willReturn(false); | ||
140 | $loginManager->method('canLogin')->willReturn(false); | ||
141 | $this->container->loginManager = $loginManager; | ||
142 | |||
143 | $this->expectException(LoginBannedException::class); | ||
144 | |||
145 | $this->controller->index($request, $response); | ||
146 | } | ||
147 | |||
148 | protected function createValidContainerMockSet(): void | ||
149 | { | ||
150 | // User logged out | ||
151 | $loginManager = $this->createMock(LoginManager::class); | ||
152 | $loginManager->method('isLoggedIn')->willReturn(false); | ||
153 | $loginManager->method('canLogin')->willReturn(true); | ||
154 | $this->container->loginManager = $loginManager; | ||
155 | |||
156 | // Config | ||
157 | $conf = $this->createMock(ConfigManager::class); | ||
158 | $conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
159 | return $default; | ||
160 | }); | ||
161 | $this->container->conf = $conf; | ||
162 | |||
163 | // PageBuilder | ||
164 | $pageBuilder = $this->createMock(PageBuilder::class); | ||
165 | $pageBuilder | ||
166 | ->method('render') | ||
167 | ->willReturnCallback(function (string $template): string { | ||
168 | return $template; | ||
169 | }) | ||
170 | ; | ||
171 | $this->container->pageBuilder = $pageBuilder; | ||
172 | |||
173 | $pluginManager = $this->createMock(PluginManager::class); | ||
174 | $this->container->pluginManager = $pluginManager; | ||
175 | $bookmarkService = $this->createMock(BookmarkServiceInterface::class); | ||
176 | $this->container->bookmarkService = $bookmarkService; | ||
177 | } | ||
178 | } | ||
diff --git a/tests/front/controller/ShaarliControllerTest.php b/tests/front/controller/ShaarliControllerTest.php deleted file mode 100644 index 6fa3feb9..00000000 --- a/tests/front/controller/ShaarliControllerTest.php +++ /dev/null | |||
@@ -1,116 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\BookmarkFilter; | ||
9 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
10 | use Shaarli\Container\ShaarliContainer; | ||
11 | use Shaarli\Plugin\PluginManager; | ||
12 | use Shaarli\Render\PageBuilder; | ||
13 | use Shaarli\Security\LoginManager; | ||
14 | |||
15 | /** | ||
16 | * Class ShaarliControllerTest | ||
17 | * | ||
18 | * This class is used to test default behavior of ShaarliController abstract class. | ||
19 | * It uses a dummy non abstract controller. | ||
20 | */ | ||
21 | class ShaarliControllerTest extends TestCase | ||
22 | { | ||
23 | /** @var ShaarliContainer */ | ||
24 | protected $container; | ||
25 | |||
26 | /** @var LoginController */ | ||
27 | protected $controller; | ||
28 | |||
29 | /** @var mixed[] List of variable assigned to the template */ | ||
30 | protected $assignedValues; | ||
31 | |||
32 | public function setUp(): void | ||
33 | { | ||
34 | $this->container = $this->createMock(ShaarliContainer::class); | ||
35 | $this->controller = new class($this->container) extends ShaarliController | ||
36 | { | ||
37 | public function assignView(string $key, $value): ShaarliController | ||
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 | $this->assignedValues = []; | ||
48 | } | ||
49 | |||
50 | public function testAssignView(): void | ||
51 | { | ||
52 | $this->createValidContainerMockSet(); | ||
53 | |||
54 | $self = $this->controller->assignView('variableName', 'variableValue'); | ||
55 | |||
56 | static::assertInstanceOf(ShaarliController::class, $self); | ||
57 | static::assertSame('variableValue', $this->assignedValues['variableName']); | ||
58 | } | ||
59 | |||
60 | public function testRender(): void | ||
61 | { | ||
62 | $this->createValidContainerMockSet(); | ||
63 | |||
64 | $render = $this->controller->render('templateName'); | ||
65 | |||
66 | static::assertSame('templateName', $render); | ||
67 | |||
68 | static::assertSame(10, $this->assignedValues['linkcount']); | ||
69 | static::assertSame(5, $this->assignedValues['privateLinkcount']); | ||
70 | static::assertSame(['error'], $this->assignedValues['plugin_errors']); | ||
71 | |||
72 | static::assertSame('templateName', $this->assignedValues['plugins_includes']['render_includes']['target']); | ||
73 | static::assertTrue($this->assignedValues['plugins_includes']['render_includes']['loggedin']); | ||
74 | static::assertSame('templateName', $this->assignedValues['plugins_header']['render_header']['target']); | ||
75 | static::assertTrue($this->assignedValues['plugins_header']['render_header']['loggedin']); | ||
76 | static::assertSame('templateName', $this->assignedValues['plugins_footer']['render_footer']['target']); | ||
77 | static::assertTrue($this->assignedValues['plugins_footer']['render_footer']['loggedin']); | ||
78 | } | ||
79 | |||
80 | protected function createValidContainerMockSet(): void | ||
81 | { | ||
82 | $pageBuilder = $this->createMock(PageBuilder::class); | ||
83 | $pageBuilder | ||
84 | ->method('assign') | ||
85 | ->willReturnCallback(function (string $key, $value): void { | ||
86 | $this->assignedValues[$key] = $value; | ||
87 | }); | ||
88 | $pageBuilder | ||
89 | ->method('render') | ||
90 | ->willReturnCallback(function (string $template): string { | ||
91 | return $template; | ||
92 | }); | ||
93 | $this->container->pageBuilder = $pageBuilder; | ||
94 | |||
95 | $bookmarkService = $this->createMock(BookmarkServiceInterface::class); | ||
96 | $bookmarkService | ||
97 | ->method('count') | ||
98 | ->willReturnCallback(function (string $visibility): int { | ||
99 | return $visibility === BookmarkFilter::$PRIVATE ? 5 : 10; | ||
100 | }); | ||
101 | $this->container->bookmarkService = $bookmarkService; | ||
102 | |||
103 | $pluginManager = $this->createMock(PluginManager::class); | ||
104 | $pluginManager | ||
105 | ->method('executeHooks') | ||
106 | ->willReturnCallback(function (string $hook, array &$data, array $params): array { | ||
107 | return $data[$hook] = $params; | ||
108 | }); | ||
109 | $pluginManager->method('getErrors')->willReturn(['error']); | ||
110 | $this->container->pluginManager = $pluginManager; | ||
111 | |||
112 | $loginManager = $this->createMock(LoginManager::class); | ||
113 | $loginManager->method('isLoggedIn')->willReturn(true); | ||
114 | $this->container->loginManager = $loginManager; | ||
115 | } | ||
116 | } | ||
diff --git a/tests/front/controller/admin/ConfigureControllerTest.php b/tests/front/controller/admin/ConfigureControllerTest.php new file mode 100644 index 00000000..f2f84bac --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Exception\WrongTokenException; | ||
10 | use Shaarli\Security\SessionManager; | ||
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(4, $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..50d9e378 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Formatter\BookmarkFormatter; | ||
10 | use Shaarli\Formatter\BookmarkRawFormatter; | ||
11 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
12 | use Shaarli\Security\SessionManager; | ||
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', $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..eb31fad0 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Psr\Http\Message\UploadedFileInterface; | ||
9 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
10 | use Shaarli\Security\SessionManager; | ||
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..45e84dc0 --- /dev/null +++ b/tests/front/controller/admin/LogoutControllerTest.php | |||
@@ -0,0 +1,51 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Security\CookieManager; | ||
9 | use Shaarli\Security\LoginManager; | ||
10 | use Shaarli\Security\SessionManager; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | |||
14 | class LogoutControllerTest extends TestCase | ||
15 | { | ||
16 | use FrontAdminControllerMockHelper; | ||
17 | |||
18 | /** @var LogoutController */ | ||
19 | protected $controller; | ||
20 | |||
21 | public function setUp(): void | ||
22 | { | ||
23 | $this->createContainer(); | ||
24 | |||
25 | $this->controller = new LogoutController($this->container); | ||
26 | } | ||
27 | |||
28 | public function testValidControllerInvoke(): void | ||
29 | { | ||
30 | $request = $this->createMock(Request::class); | ||
31 | $response = new Response(); | ||
32 | |||
33 | $this->container->pageCacheManager->expects(static::once())->method('invalidateCaches'); | ||
34 | |||
35 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
36 | $this->container->sessionManager->expects(static::once())->method('logout'); | ||
37 | |||
38 | $this->container->cookieManager = $this->createMock(CookieManager::class); | ||
39 | $this->container->cookieManager | ||
40 | ->expects(static::once()) | ||
41 | ->method('setCookieParameter') | ||
42 | ->with(CookieManager::STAY_SIGNED_IN, 'false', 0, '/subfolder/') | ||
43 | ; | ||
44 | |||
45 | $result = $this->controller->index($request, $response); | ||
46 | |||
47 | static::assertInstanceOf(Response::class, $result); | ||
48 | static::assertSame(302, $result->getStatusCode()); | ||
49 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
50 | } | ||
51 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php b/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php new file mode 100644 index 00000000..7d5b752a --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
9 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
10 | use Shaarli\Http\HttpAccess; | ||
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..5a615791 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
10 | use Shaarli\Formatter\BookmarkFormatter; | ||
11 | use Shaarli\Formatter\BookmarkRawFormatter; | ||
12 | use Shaarli\Formatter\FormatterFactory; | ||
13 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
14 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
15 | use Shaarli\Http\HttpAccess; | ||
16 | use Shaarli\Security\SessionManager; | ||
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..dee622bb --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
10 | use Shaarli\Formatter\BookmarkFormatter; | ||
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 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..777583d5 --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/DisplayCreateFormTest.php | |||
@@ -0,0 +1,315 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
11 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
12 | use Shaarli\Http\HttpAccess; | ||
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::at(0)) | ||
100 | ->method('executeHooks') | ||
101 | ->willReturnCallback(function (string $hook, array $data) use ($remoteTitle, $remoteDesc): array { | ||
102 | static::assertSame('render_editlink', $hook); | ||
103 | static::assertSame($remoteTitle, $data['link']['title']); | ||
104 | static::assertSame($remoteDesc, $data['link']['description']); | ||
105 | |||
106 | return $data; | ||
107 | }) | ||
108 | ; | ||
109 | |||
110 | $result = $this->controller->displayCreateForm($request, $response); | ||
111 | |||
112 | static::assertSame(200, $result->getStatusCode()); | ||
113 | static::assertSame('editlink', (string) $result->getBody()); | ||
114 | |||
115 | static::assertSame('Shaare - Shaarli', $assignedVariables['pagetitle']); | ||
116 | |||
117 | static::assertSame($expectedUrl, $assignedVariables['link']['url']); | ||
118 | static::assertSame($remoteTitle, $assignedVariables['link']['title']); | ||
119 | static::assertSame($remoteDesc, $assignedVariables['link']['description']); | ||
120 | static::assertSame($remoteTags, $assignedVariables['link']['tags']); | ||
121 | static::assertFalse($assignedVariables['link']['private']); | ||
122 | |||
123 | static::assertTrue($assignedVariables['link_is_new']); | ||
124 | static::assertSame($referer, $assignedVariables['http_referer']); | ||
125 | static::assertSame($tags, $assignedVariables['tags']); | ||
126 | static::assertArrayHasKey('source', $assignedVariables); | ||
127 | static::assertArrayHasKey('default_private_links', $assignedVariables); | ||
128 | } | ||
129 | |||
130 | /** | ||
131 | * Test displaying bookmark create form | ||
132 | * Ensure all available query parameters are handled properly. | ||
133 | */ | ||
134 | public function testDisplayCreateFormWithFullParameters(): void | ||
135 | { | ||
136 | $assignedVariables = []; | ||
137 | $this->assignTemplateVars($assignedVariables); | ||
138 | |||
139 | $parameters = [ | ||
140 | 'post' => 'http://url.tld/other?part=3&utm_ad=pay#hash', | ||
141 | 'title' => 'Provided Title', | ||
142 | 'description' => 'Provided description.', | ||
143 | 'tags' => 'abc def', | ||
144 | 'private' => '1', | ||
145 | 'source' => 'apps', | ||
146 | ]; | ||
147 | $expectedUrl = str_replace('&utm_ad=pay', '', $parameters['post']); | ||
148 | |||
149 | $request = $this->createMock(Request::class); | ||
150 | $request | ||
151 | ->method('getParam') | ||
152 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
153 | return $parameters[$key] ?? null; | ||
154 | }); | ||
155 | $response = new Response(); | ||
156 | |||
157 | $result = $this->controller->displayCreateForm($request, $response); | ||
158 | |||
159 | static::assertSame(200, $result->getStatusCode()); | ||
160 | static::assertSame('editlink', (string) $result->getBody()); | ||
161 | |||
162 | static::assertSame('Shaare - Shaarli', $assignedVariables['pagetitle']); | ||
163 | |||
164 | static::assertSame($expectedUrl, $assignedVariables['link']['url']); | ||
165 | static::assertSame($parameters['title'], $assignedVariables['link']['title']); | ||
166 | static::assertSame($parameters['description'], $assignedVariables['link']['description']); | ||
167 | static::assertSame($parameters['tags'], $assignedVariables['link']['tags']); | ||
168 | static::assertTrue($assignedVariables['link']['private']); | ||
169 | static::assertTrue($assignedVariables['link_is_new']); | ||
170 | static::assertSame($parameters['source'], $assignedVariables['source']); | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Test displaying bookmark create form | ||
175 | * Without any parameter. | ||
176 | */ | ||
177 | public function testDisplayCreateFormEmpty(): void | ||
178 | { | ||
179 | $assignedVariables = []; | ||
180 | $this->assignTemplateVars($assignedVariables); | ||
181 | |||
182 | $request = $this->createMock(Request::class); | ||
183 | $response = new Response(); | ||
184 | |||
185 | $this->container->httpAccess->expects(static::never())->method('getHttpResponse'); | ||
186 | $this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback'); | ||
187 | |||
188 | $result = $this->controller->displayCreateForm($request, $response); | ||
189 | |||
190 | static::assertSame(200, $result->getStatusCode()); | ||
191 | static::assertSame('editlink', (string) $result->getBody()); | ||
192 | static::assertSame('', $assignedVariables['link']['url']); | ||
193 | static::assertSame('Note: ', $assignedVariables['link']['title']); | ||
194 | static::assertSame('', $assignedVariables['link']['description']); | ||
195 | static::assertSame('', $assignedVariables['link']['tags']); | ||
196 | static::assertFalse($assignedVariables['link']['private']); | ||
197 | static::assertTrue($assignedVariables['link_is_new']); | ||
198 | } | ||
199 | |||
200 | /** | ||
201 | * Test displaying bookmark create form | ||
202 | * URL not using HTTP protocol: do not try to retrieve the title | ||
203 | */ | ||
204 | public function testDisplayCreateFormNotHttp(): void | ||
205 | { | ||
206 | $assignedVariables = []; | ||
207 | $this->assignTemplateVars($assignedVariables); | ||
208 | |||
209 | $url = 'magnet://kubuntu.torrent'; | ||
210 | $request = $this->createMock(Request::class); | ||
211 | $request | ||
212 | ->method('getParam') | ||
213 | ->willReturnCallback(function (string $key) use ($url): ?string { | ||
214 | return $key === 'post' ? $url : null; | ||
215 | }); | ||
216 | $response = new Response(); | ||
217 | |||
218 | $this->container->httpAccess->expects(static::never())->method('getHttpResponse'); | ||
219 | $this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback'); | ||
220 | |||
221 | $result = $this->controller->displayCreateForm($request, $response); | ||
222 | |||
223 | static::assertSame(200, $result->getStatusCode()); | ||
224 | static::assertSame('editlink', (string) $result->getBody()); | ||
225 | static::assertSame($url, $assignedVariables['link']['url']); | ||
226 | static::assertTrue($assignedVariables['link_is_new']); | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * Test displaying bookmark create form | ||
231 | * When markdown formatter is enabled, the no markdown tag should be added to existing tags. | ||
232 | */ | ||
233 | public function testDisplayCreateFormWithMarkdownEnabled(): void | ||
234 | { | ||
235 | $assignedVariables = []; | ||
236 | $this->assignTemplateVars($assignedVariables); | ||
237 | |||
238 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
239 | $this->container->conf | ||
240 | ->expects(static::atLeastOnce()) | ||
241 | ->method('get')->willReturnCallback(function (string $key): ?string { | ||
242 | if ($key === 'formatter') { | ||
243 | return 'markdown'; | ||
244 | } | ||
245 | |||
246 | return $key; | ||
247 | }) | ||
248 | ; | ||
249 | |||
250 | $request = $this->createMock(Request::class); | ||
251 | $response = new Response(); | ||
252 | |||
253 | $result = $this->controller->displayCreateForm($request, $response); | ||
254 | |||
255 | static::assertSame(200, $result->getStatusCode()); | ||
256 | static::assertSame('editlink', (string) $result->getBody()); | ||
257 | static::assertSame(['nomarkdown' => 1], $assignedVariables['tags']); | ||
258 | } | ||
259 | |||
260 | /** | ||
261 | * Test displaying bookmark create form | ||
262 | * When an existing URL is submitted, we want to edit the existing link. | ||
263 | */ | ||
264 | public function testDisplayCreateFormWithExistingUrl(): void | ||
265 | { | ||
266 | $assignedVariables = []; | ||
267 | $this->assignTemplateVars($assignedVariables); | ||
268 | |||
269 | $url = 'http://url.tld/other?part=3&utm_ad=pay#hash'; | ||
270 | $expectedUrl = str_replace('&utm_ad=pay', '', $url); | ||
271 | |||
272 | $request = $this->createMock(Request::class); | ||
273 | $request | ||
274 | ->method('getParam') | ||
275 | ->willReturnCallback(function (string $key) use ($url): ?string { | ||
276 | return $key === 'post' ? $url : null; | ||
277 | }); | ||
278 | $response = new Response(); | ||
279 | |||
280 | $this->container->httpAccess->expects(static::never())->method('getHttpResponse'); | ||
281 | $this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback'); | ||
282 | |||
283 | $this->container->bookmarkService | ||
284 | ->expects(static::once()) | ||
285 | ->method('findByUrl') | ||
286 | ->with($expectedUrl) | ||
287 | ->willReturn( | ||
288 | (new Bookmark()) | ||
289 | ->setId($id = 23) | ||
290 | ->setUrl($expectedUrl) | ||
291 | ->setTitle($title = 'Bookmark Title') | ||
292 | ->setDescription($description = 'Bookmark description.') | ||
293 | ->setTags($tags = ['abc', 'def']) | ||
294 | ->setPrivate(true) | ||
295 | ->setCreated($createdAt = new \DateTime('2020-06-10 18:45:44')) | ||
296 | ) | ||
297 | ; | ||
298 | |||
299 | $result = $this->controller->displayCreateForm($request, $response); | ||
300 | |||
301 | static::assertSame(200, $result->getStatusCode()); | ||
302 | static::assertSame('editlink', (string) $result->getBody()); | ||
303 | |||
304 | static::assertSame('Edit Shaare - Shaarli', $assignedVariables['pagetitle']); | ||
305 | static::assertFalse($assignedVariables['link_is_new']); | ||
306 | |||
307 | static::assertSame($id, $assignedVariables['link']['id']); | ||
308 | static::assertSame($expectedUrl, $assignedVariables['link']['url']); | ||
309 | static::assertSame($title, $assignedVariables['link']['title']); | ||
310 | static::assertSame($description, $assignedVariables['link']['description']); | ||
311 | static::assertSame(implode(' ', $tags), $assignedVariables['link']['tags']); | ||
312 | static::assertTrue($assignedVariables['link']['private']); | ||
313 | static::assertSame($createdAt, $assignedVariables['link']['created']); | ||
314 | } | ||
315 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DisplayEditFormTest.php b/tests/front/controller/admin/ManageShaareControllerTest/DisplayEditFormTest.php new file mode 100644 index 00000000..1a1cdcf3 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
10 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
11 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
12 | use Shaarli\Http\HttpAccess; | ||
13 | use Shaarli\Security\SessionManager; | ||
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..1607b475 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
10 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
11 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
12 | use Shaarli\Http\HttpAccess; | ||
13 | use Shaarli\Security\SessionManager; | ||
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..dabcd60d --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php | |||
@@ -0,0 +1,282 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
11 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
12 | use Shaarli\Front\Exception\WrongTokenException; | ||
13 | use Shaarli\Http\HttpAccess; | ||
14 | use Shaarli\Security\SessionManager; | ||
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.tld/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::at(0)) | ||
92 | ->method('executeHooks') | ||
93 | ->willReturnCallback(function (string $hook, array $data) use ($parameters, $id): array { | ||
94 | static::assertSame('save_link', $hook); | ||
95 | |||
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 | return $data; | ||
104 | }) | ||
105 | ; | ||
106 | |||
107 | $result = $this->controller->save($request, $response); | ||
108 | |||
109 | static::assertSame(302, $result->getStatusCode()); | ||
110 | static::assertRegExp('@/subfolder/#[\w\-]{6}@', $result->getHeader('location')[0]); | ||
111 | } | ||
112 | |||
113 | |||
114 | /** | ||
115 | * Test save an existing bookmark | ||
116 | */ | ||
117 | public function testSaveExistingBookmark(): void | ||
118 | { | ||
119 | $id = 21; | ||
120 | $parameters = [ | ||
121 | 'lf_id' => (string) $id, | ||
122 | 'lf_url' => 'http://url.tld/other?part=3#hash', | ||
123 | 'lf_title' => 'Provided Title', | ||
124 | 'lf_description' => 'Provided description.', | ||
125 | 'lf_tags' => 'abc def', | ||
126 | 'lf_private' => '1', | ||
127 | 'returnurl' => 'http://shaarli.tld/subfolder/?page=2' | ||
128 | ]; | ||
129 | |||
130 | $request = $this->createMock(Request::class); | ||
131 | $request | ||
132 | ->method('getParam') | ||
133 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
134 | return $parameters[$key] ?? null; | ||
135 | }) | ||
136 | ; | ||
137 | $response = new Response(); | ||
138 | |||
139 | $checkBookmark = function (Bookmark $bookmark) use ($parameters, $id) { | ||
140 | static::assertSame($id, $bookmark->getId()); | ||
141 | static::assertSame($parameters['lf_url'], $bookmark->getUrl()); | ||
142 | static::assertSame($parameters['lf_title'], $bookmark->getTitle()); | ||
143 | static::assertSame($parameters['lf_description'], $bookmark->getDescription()); | ||
144 | static::assertSame($parameters['lf_tags'], $bookmark->getTagsString()); | ||
145 | static::assertTrue($bookmark->isPrivate()); | ||
146 | }; | ||
147 | |||
148 | $this->container->bookmarkService->expects(static::atLeastOnce())->method('exists')->willReturn(true); | ||
149 | $this->container->bookmarkService | ||
150 | ->expects(static::once()) | ||
151 | ->method('get') | ||
152 | ->willReturn((new Bookmark())->setId($id)->setUrl('http://other.url')) | ||
153 | ; | ||
154 | $this->container->bookmarkService | ||
155 | ->expects(static::once()) | ||
156 | ->method('addOrSet') | ||
157 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { | ||
158 | static::assertFalse($save); | ||
159 | |||
160 | $checkBookmark($bookmark); | ||
161 | }) | ||
162 | ; | ||
163 | $this->container->bookmarkService | ||
164 | ->expects(static::once()) | ||
165 | ->method('set') | ||
166 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { | ||
167 | static::assertTrue($save); | ||
168 | |||
169 | $checkBookmark($bookmark); | ||
170 | |||
171 | static::assertSame($id, $bookmark->getId()); | ||
172 | }) | ||
173 | ; | ||
174 | |||
175 | // Make sure that PluginManager hook is triggered | ||
176 | $this->container->pluginManager | ||
177 | ->expects(static::at(0)) | ||
178 | ->method('executeHooks') | ||
179 | ->willReturnCallback(function (string $hook, array $data) use ($parameters, $id): array { | ||
180 | static::assertSame('save_link', $hook); | ||
181 | |||
182 | static::assertSame($id, $data['id']); | ||
183 | static::assertSame($parameters['lf_url'], $data['url']); | ||
184 | static::assertSame($parameters['lf_title'], $data['title']); | ||
185 | static::assertSame($parameters['lf_description'], $data['description']); | ||
186 | static::assertSame($parameters['lf_tags'], $data['tags']); | ||
187 | static::assertTrue($data['private']); | ||
188 | |||
189 | return $data; | ||
190 | }) | ||
191 | ; | ||
192 | |||
193 | $result = $this->controller->save($request, $response); | ||
194 | |||
195 | static::assertSame(302, $result->getStatusCode()); | ||
196 | static::assertRegExp('@/subfolder/\?page=2#[\w\-]{6}@', $result->getHeader('location')[0]); | ||
197 | } | ||
198 | |||
199 | /** | ||
200 | * Test save a bookmark - try to retrieve the thumbnail | ||
201 | */ | ||
202 | public function testSaveBookmarkWithThumbnail(): void | ||
203 | { | ||
204 | $parameters = ['lf_url' => 'http://url.tld/other?part=3#hash']; | ||
205 | |||
206 | $request = $this->createMock(Request::class); | ||
207 | $request | ||
208 | ->method('getParam') | ||
209 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
210 | return $parameters[$key] ?? null; | ||
211 | }) | ||
212 | ; | ||
213 | $response = new Response(); | ||
214 | |||
215 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
216 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
217 | return $key === 'thumbnails.mode' ? Thumbnailer::MODE_ALL : $default; | ||
218 | }); | ||
219 | |||
220 | $this->container->thumbnailer = $this->createMock(Thumbnailer::class); | ||
221 | $this->container->thumbnailer | ||
222 | ->expects(static::once()) | ||
223 | ->method('get') | ||
224 | ->with($parameters['lf_url']) | ||
225 | ->willReturn($thumb = 'http://thumb.url') | ||
226 | ; | ||
227 | |||
228 | $this->container->bookmarkService | ||
229 | ->expects(static::once()) | ||
230 | ->method('addOrSet') | ||
231 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($thumb): void { | ||
232 | static::assertSame($thumb, $bookmark->getThumbnail()); | ||
233 | }) | ||
234 | ; | ||
235 | |||
236 | $result = $this->controller->save($request, $response); | ||
237 | |||
238 | static::assertSame(302, $result->getStatusCode()); | ||
239 | } | ||
240 | |||
241 | /** | ||
242 | * Change the password with a wrong existing password | ||
243 | */ | ||
244 | public function testSaveBookmarkFromBookmarklet(): void | ||
245 | { | ||
246 | $parameters = ['source' => 'bookmarklet']; | ||
247 | |||
248 | $request = $this->createMock(Request::class); | ||
249 | $request | ||
250 | ->method('getParam') | ||
251 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
252 | return $parameters[$key] ?? null; | ||
253 | }) | ||
254 | ; | ||
255 | $response = new Response(); | ||
256 | |||
257 | $result = $this->controller->save($request, $response); | ||
258 | |||
259 | static::assertSame(200, $result->getStatusCode()); | ||
260 | static::assertSame('<script>self.close();</script>', (string) $result->getBody()); | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * Change the password with a wrong existing password | ||
265 | */ | ||
266 | public function testSaveBookmarkWrongToken(): void | ||
267 | { | ||
268 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
269 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
270 | |||
271 | $this->container->bookmarkService->expects(static::never())->method('addOrSet'); | ||
272 | $this->container->bookmarkService->expects(static::never())->method('set'); | ||
273 | |||
274 | $request = $this->createMock(Request::class); | ||
275 | $response = new Response(); | ||
276 | |||
277 | $this->expectException(WrongTokenException::class); | ||
278 | |||
279 | $this->controller->save($request, $response); | ||
280 | } | ||
281 | |||
282 | } | ||
diff --git a/tests/front/controller/admin/ManageTagControllerTest.php b/tests/front/controller/admin/ManageTagControllerTest.php new file mode 100644 index 00000000..09ba0b4b --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\BookmarkFilter; | ||
10 | use Shaarli\Front\Exception\WrongTokenException; | ||
11 | use Shaarli\Security\SessionManager; | ||
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..9a01089e --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Exception\OpenShaarliPasswordException; | ||
10 | use Shaarli\Front\Exception\WrongTokenException; | ||
11 | use Shaarli\Security\SessionManager; | ||
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..5b59285c --- /dev/null +++ b/tests/front/controller/admin/PluginsControllerTest.php | |||
@@ -0,0 +1,204 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Exception\WrongTokenException; | ||
10 | use Shaarli\Plugin\PluginManager; | ||
11 | use Shaarli\Security\SessionManager; | ||
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() | ||
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 | ]; | ||
129 | |||
130 | $request = $this->createMock(Request::class); | ||
131 | $request | ||
132 | ->expects(static::atLeastOnce()) | ||
133 | ->method('getParams') | ||
134 | ->willReturnCallback(function () use ($parameters): array { | ||
135 | return $parameters; | ||
136 | }) | ||
137 | ; | ||
138 | $response = new Response(); | ||
139 | |||
140 | $this->container->pluginManager | ||
141 | ->expects(static::once()) | ||
142 | ->method('executeHooks') | ||
143 | ->with('save_plugin_parameters', $parameters) | ||
144 | ; | ||
145 | $this->container->conf | ||
146 | ->expects(static::atLeastOnce()) | ||
147 | ->method('set') | ||
148 | ->withConsecutive(['plugins.parameter1', 'blip'], ['plugins.parameter2', 'blop']) | ||
149 | ; | ||
150 | |||
151 | $result = $this->controller->save($request, $response); | ||
152 | |||
153 | static::assertSame(302, $result->getStatusCode()); | ||
154 | static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location')); | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * Test save plugin parameters - error encountered | ||
159 | */ | ||
160 | public function testSaveWithError(): void | ||
161 | { | ||
162 | $request = $this->createMock(Request::class); | ||
163 | $response = new Response(); | ||
164 | |||
165 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
166 | $this->container->conf | ||
167 | ->expects(static::atLeastOnce()) | ||
168 | ->method('write') | ||
169 | ->willThrowException(new \Exception($message = 'error message')) | ||
170 | ; | ||
171 | |||
172 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
173 | $this->container->sessionManager->method('checkToken')->willReturn(true); | ||
174 | $this->container->sessionManager | ||
175 | ->expects(static::once()) | ||
176 | ->method('setSessionParameter') | ||
177 | ->with( | ||
178 | SessionManager::KEY_ERROR_MESSAGES, | ||
179 | ['Error while saving plugin configuration: ' . PHP_EOL . $message] | ||
180 | ) | ||
181 | ; | ||
182 | |||
183 | $result = $this->controller->save($request, $response); | ||
184 | |||
185 | static::assertSame(302, $result->getStatusCode()); | ||
186 | static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location')); | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * Test save plugin parameters - wrong token | ||
191 | */ | ||
192 | public function testSaveWrongToken(): void | ||
193 | { | ||
194 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
195 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
196 | |||
197 | $request = $this->createMock(Request::class); | ||
198 | $response = new Response(); | ||
199 | |||
200 | $this->expectException(WrongTokenException::class); | ||
201 | |||
202 | $this->controller->save($request, $response); | ||
203 | } | ||
204 | } | ||
diff --git a/tests/front/controller/admin/SessionFilterControllerTest.php b/tests/front/controller/admin/SessionFilterControllerTest.php new file mode 100644 index 00000000..d306c6e9 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Security\LoginManager; | ||
9 | use Shaarli\Security\SessionManager; | ||
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..fff427cb --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Front\Exception\WrongTokenException; | ||
9 | use Shaarli\Security\SessionManager; | ||
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..0c0c8a83 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
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..04b0c0fa --- /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 PHPUnit\Framework\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..fc756f0f --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | class ToolsControllerTestControllerTest 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..5daaa2c4 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
10 | use Shaarli\Config\ConfigManager; | ||
11 | use Shaarli\Security\LoginManager; | ||
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..b802c62c --- /dev/null +++ b/tests/front/controller/visitor/DailyControllerTest.php | |||
@@ -0,0 +1,476 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Feed\CachedPage; | ||
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::at(0)) | ||
82 | ->method('executeHooks') | ||
83 | ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array { | ||
84 | static::assertSame('render_daily', $hook); | ||
85 | |||
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 | return $data; | ||
96 | }) | ||
97 | ; | ||
98 | |||
99 | $result = $this->controller->index($request, $response); | ||
100 | |||
101 | static::assertSame(200, $result->getStatusCode()); | ||
102 | static::assertSame('daily', (string) $result->getBody()); | ||
103 | static::assertSame( | ||
104 | 'Daily - '. format_date($currentDay, false, true) .' - Shaarli', | ||
105 | $assignedVariables['pagetitle'] | ||
106 | ); | ||
107 | static::assertEquals($currentDay, $assignedVariables['dayDate']); | ||
108 | static::assertEquals($currentDay->getTimestamp(), $assignedVariables['day']); | ||
109 | static::assertCount(3, $assignedVariables['linksToDisplay']); | ||
110 | |||
111 | $link = $assignedVariables['linksToDisplay'][0]; | ||
112 | |||
113 | static::assertSame(1, $link['id']); | ||
114 | static::assertSame('http://url.tld', $link['url']); | ||
115 | static::assertNotEmpty($link['title']); | ||
116 | static::assertNotEmpty($link['description']); | ||
117 | static::assertNotEmpty($link['formatedDescription']); | ||
118 | |||
119 | $link = $assignedVariables['linksToDisplay'][1]; | ||
120 | |||
121 | static::assertSame(2, $link['id']); | ||
122 | static::assertSame('http://url2.tld', $link['url']); | ||
123 | static::assertNotEmpty($link['title']); | ||
124 | static::assertNotEmpty($link['description']); | ||
125 | static::assertNotEmpty($link['formatedDescription']); | ||
126 | |||
127 | $link = $assignedVariables['linksToDisplay'][2]; | ||
128 | |||
129 | static::assertSame(3, $link['id']); | ||
130 | static::assertSame('http://url3.tld', $link['url']); | ||
131 | static::assertNotEmpty($link['title']); | ||
132 | static::assertNotEmpty($link['description']); | ||
133 | static::assertNotEmpty($link['formatedDescription']); | ||
134 | |||
135 | static::assertCount(3, $assignedVariables['cols']); | ||
136 | static::assertCount(1, $assignedVariables['cols'][0]); | ||
137 | static::assertCount(1, $assignedVariables['cols'][1]); | ||
138 | static::assertCount(1, $assignedVariables['cols'][2]); | ||
139 | |||
140 | $link = $assignedVariables['cols'][0][0]; | ||
141 | |||
142 | static::assertSame(1, $link['id']); | ||
143 | static::assertSame('http://url.tld', $link['url']); | ||
144 | static::assertNotEmpty($link['title']); | ||
145 | static::assertNotEmpty($link['description']); | ||
146 | static::assertNotEmpty($link['formatedDescription']); | ||
147 | |||
148 | $link = $assignedVariables['cols'][1][0]; | ||
149 | |||
150 | static::assertSame(2, $link['id']); | ||
151 | static::assertSame('http://url2.tld', $link['url']); | ||
152 | static::assertNotEmpty($link['title']); | ||
153 | static::assertNotEmpty($link['description']); | ||
154 | static::assertNotEmpty($link['formatedDescription']); | ||
155 | |||
156 | $link = $assignedVariables['cols'][2][0]; | ||
157 | |||
158 | static::assertSame(3, $link['id']); | ||
159 | static::assertSame('http://url3.tld', $link['url']); | ||
160 | static::assertNotEmpty($link['title']); | ||
161 | static::assertNotEmpty($link['description']); | ||
162 | static::assertNotEmpty($link['formatedDescription']); | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * Daily page - test that everything goes fine with no future or past bookmarks | ||
167 | */ | ||
168 | public function testValidIndexControllerInvokeNoFutureOrPast(): void | ||
169 | { | ||
170 | $currentDay = new \DateTimeImmutable('2020-05-13'); | ||
171 | |||
172 | $request = $this->createMock(Request::class); | ||
173 | $response = new Response(); | ||
174 | |||
175 | // Save RainTPL assigned variables | ||
176 | $assignedVariables = []; | ||
177 | $this->assignTemplateVars($assignedVariables); | ||
178 | |||
179 | // Links dataset: 2 links with thumbnails | ||
180 | $this->container->bookmarkService | ||
181 | ->expects(static::once()) | ||
182 | ->method('days') | ||
183 | ->willReturnCallback(function () use ($currentDay): array { | ||
184 | return [ | ||
185 | $currentDay->format($currentDay->format('Ymd')), | ||
186 | ]; | ||
187 | }) | ||
188 | ; | ||
189 | $this->container->bookmarkService | ||
190 | ->expects(static::once()) | ||
191 | ->method('filterDay') | ||
192 | ->willReturnCallback(function (): array { | ||
193 | return [ | ||
194 | (new Bookmark()) | ||
195 | ->setId(1) | ||
196 | ->setUrl('http://url.tld') | ||
197 | ->setTitle(static::generateString(50)) | ||
198 | ->setDescription(static::generateString(500)) | ||
199 | , | ||
200 | ]; | ||
201 | }) | ||
202 | ; | ||
203 | |||
204 | // Make sure that PluginManager hook is triggered | ||
205 | $this->container->pluginManager | ||
206 | ->expects(static::at(0)) | ||
207 | ->method('executeHooks') | ||
208 | ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array { | ||
209 | static::assertSame('render_daily', $hook); | ||
210 | |||
211 | static::assertArrayHasKey('linksToDisplay', $data); | ||
212 | static::assertCount(1, $data['linksToDisplay']); | ||
213 | static::assertSame(1, $data['linksToDisplay'][0]['id']); | ||
214 | static::assertSame($currentDay->getTimestamp(), $data['day']); | ||
215 | static::assertEmpty($data['previousday']); | ||
216 | static::assertEmpty($data['nextday']); | ||
217 | |||
218 | static::assertArrayHasKey('loggedin', $param); | ||
219 | |||
220 | return $data; | ||
221 | }); | ||
222 | |||
223 | $result = $this->controller->index($request, $response); | ||
224 | |||
225 | static::assertSame(200, $result->getStatusCode()); | ||
226 | static::assertSame('daily', (string) $result->getBody()); | ||
227 | static::assertSame( | ||
228 | 'Daily - '. format_date($currentDay, false, true) .' - Shaarli', | ||
229 | $assignedVariables['pagetitle'] | ||
230 | ); | ||
231 | static::assertCount(1, $assignedVariables['linksToDisplay']); | ||
232 | |||
233 | $link = $assignedVariables['linksToDisplay'][0]; | ||
234 | static::assertSame(1, $link['id']); | ||
235 | } | ||
236 | |||
237 | /** | ||
238 | * Daily page - test that height adjustment in columns is working | ||
239 | */ | ||
240 | public function testValidIndexControllerInvokeHeightAdjustment(): void | ||
241 | { | ||
242 | $currentDay = new \DateTimeImmutable('2020-05-13'); | ||
243 | |||
244 | $request = $this->createMock(Request::class); | ||
245 | $response = new Response(); | ||
246 | |||
247 | // Save RainTPL assigned variables | ||
248 | $assignedVariables = []; | ||
249 | $this->assignTemplateVars($assignedVariables); | ||
250 | |||
251 | // Links dataset: 2 links with thumbnails | ||
252 | $this->container->bookmarkService | ||
253 | ->expects(static::once()) | ||
254 | ->method('days') | ||
255 | ->willReturnCallback(function () use ($currentDay): array { | ||
256 | return [ | ||
257 | $currentDay->format($currentDay->format('Ymd')), | ||
258 | ]; | ||
259 | }) | ||
260 | ; | ||
261 | $this->container->bookmarkService | ||
262 | ->expects(static::once()) | ||
263 | ->method('filterDay') | ||
264 | ->willReturnCallback(function (): array { | ||
265 | return [ | ||
266 | (new Bookmark())->setId(1)->setUrl('http://url.tld')->setTitle('title'), | ||
267 | (new Bookmark()) | ||
268 | ->setId(2) | ||
269 | ->setUrl('http://url.tld') | ||
270 | ->setTitle(static::generateString(50)) | ||
271 | ->setDescription(static::generateString(5000)) | ||
272 | , | ||
273 | (new Bookmark())->setId(3)->setUrl('http://url.tld')->setTitle('title'), | ||
274 | (new Bookmark())->setId(4)->setUrl('http://url.tld')->setTitle('title'), | ||
275 | (new Bookmark())->setId(5)->setUrl('http://url.tld')->setTitle('title'), | ||
276 | (new Bookmark())->setId(6)->setUrl('http://url.tld')->setTitle('title'), | ||
277 | (new Bookmark())->setId(7)->setUrl('http://url.tld')->setTitle('title'), | ||
278 | ]; | ||
279 | }) | ||
280 | ; | ||
281 | |||
282 | // Make sure that PluginManager hook is triggered | ||
283 | $this->container->pluginManager | ||
284 | ->expects(static::at(0)) | ||
285 | ->method('executeHooks') | ||
286 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
287 | return $data; | ||
288 | }) | ||
289 | ; | ||
290 | |||
291 | $result = $this->controller->index($request, $response); | ||
292 | |||
293 | static::assertSame(200, $result->getStatusCode()); | ||
294 | static::assertSame('daily', (string) $result->getBody()); | ||
295 | static::assertCount(7, $assignedVariables['linksToDisplay']); | ||
296 | |||
297 | $columnIds = function (array $column): array { | ||
298 | return array_map(function (array $item): int { return $item['id']; }, $column); | ||
299 | }; | ||
300 | |||
301 | static::assertSame([1, 4, 6], $columnIds($assignedVariables['cols'][0])); | ||
302 | static::assertSame([2], $columnIds($assignedVariables['cols'][1])); | ||
303 | static::assertSame([3, 5, 7], $columnIds($assignedVariables['cols'][2])); | ||
304 | } | ||
305 | |||
306 | /** | ||
307 | * Daily page - no bookmark | ||
308 | */ | ||
309 | public function testValidIndexControllerInvokeNoBookmark(): void | ||
310 | { | ||
311 | $request = $this->createMock(Request::class); | ||
312 | $response = new Response(); | ||
313 | |||
314 | // Save RainTPL assigned variables | ||
315 | $assignedVariables = []; | ||
316 | $this->assignTemplateVars($assignedVariables); | ||
317 | |||
318 | // Links dataset: 2 links with thumbnails | ||
319 | $this->container->bookmarkService | ||
320 | ->expects(static::once()) | ||
321 | ->method('days') | ||
322 | ->willReturnCallback(function (): array { | ||
323 | return []; | ||
324 | }) | ||
325 | ; | ||
326 | $this->container->bookmarkService | ||
327 | ->expects(static::once()) | ||
328 | ->method('filterDay') | ||
329 | ->willReturnCallback(function (): array { | ||
330 | return []; | ||
331 | }) | ||
332 | ; | ||
333 | |||
334 | // Make sure that PluginManager hook is triggered | ||
335 | $this->container->pluginManager | ||
336 | ->expects(static::at(0)) | ||
337 | ->method('executeHooks') | ||
338 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
339 | return $data; | ||
340 | }) | ||
341 | ; | ||
342 | |||
343 | $result = $this->controller->index($request, $response); | ||
344 | |||
345 | static::assertSame(200, $result->getStatusCode()); | ||
346 | static::assertSame('daily', (string) $result->getBody()); | ||
347 | static::assertCount(0, $assignedVariables['linksToDisplay']); | ||
348 | static::assertSame('Today', $assignedVariables['dayDesc']); | ||
349 | static::assertEquals((new \DateTime())->setTime(0, 0)->getTimestamp(), $assignedVariables['day']); | ||
350 | static::assertEquals((new \DateTime())->setTime(0, 0), $assignedVariables['dayDate']); | ||
351 | } | ||
352 | |||
353 | /** | ||
354 | * Daily RSS - default behaviour | ||
355 | */ | ||
356 | public function testValidRssControllerInvokeDefault(): void | ||
357 | { | ||
358 | $dates = [ | ||
359 | new \DateTimeImmutable('2020-05-17'), | ||
360 | new \DateTimeImmutable('2020-05-15'), | ||
361 | new \DateTimeImmutable('2020-05-13'), | ||
362 | ]; | ||
363 | |||
364 | $request = $this->createMock(Request::class); | ||
365 | $response = new Response(); | ||
366 | |||
367 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([ | ||
368 | (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'), | ||
369 | (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'), | ||
370 | (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'), | ||
371 | (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'), | ||
372 | ]); | ||
373 | |||
374 | $this->container->pageCacheManager | ||
375 | ->expects(static::once()) | ||
376 | ->method('getCachePage') | ||
377 | ->willReturnCallback(function (): CachedPage { | ||
378 | $cachedPage = $this->createMock(CachedPage::class); | ||
379 | $cachedPage->expects(static::once())->method('cache')->with('dailyrss'); | ||
380 | |||
381 | return $cachedPage; | ||
382 | } | ||
383 | ); | ||
384 | |||
385 | // Save RainTPL assigned variables | ||
386 | $assignedVariables = []; | ||
387 | $this->assignTemplateVars($assignedVariables); | ||
388 | |||
389 | $result = $this->controller->rss($request, $response); | ||
390 | |||
391 | static::assertSame(200, $result->getStatusCode()); | ||
392 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
393 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
394 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
395 | static::assertSame('http://shaarli', $assignedVariables['index_url']); | ||
396 | static::assertSame('http://shaarli/daily-rss', $assignedVariables['page_url']); | ||
397 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
398 | static::assertCount(2, $assignedVariables['days']); | ||
399 | |||
400 | $day = $assignedVariables['days'][$dates[0]->format('Ymd')]; | ||
401 | |||
402 | static::assertEquals($dates[0], $day['date']); | ||
403 | static::assertSame($dates[0]->format(\DateTime::RSS), $day['date_rss']); | ||
404 | static::assertSame(format_date($dates[0], false), $day['date_human']); | ||
405 | static::assertSame('http://shaarli/daily?day='. $dates[0]->format('Ymd'), $day['absolute_url']); | ||
406 | static::assertCount(1, $day['links']); | ||
407 | static::assertSame(1, $day['links'][0]['id']); | ||
408 | static::assertSame('http://domain.tld/1', $day['links'][0]['url']); | ||
409 | static::assertEquals($dates[0], $day['links'][0]['created']); | ||
410 | |||
411 | $day = $assignedVariables['days'][$dates[1]->format('Ymd')]; | ||
412 | |||
413 | static::assertEquals($dates[1], $day['date']); | ||
414 | static::assertSame($dates[1]->format(\DateTime::RSS), $day['date_rss']); | ||
415 | static::assertSame(format_date($dates[1], false), $day['date_human']); | ||
416 | static::assertSame('http://shaarli/daily?day='. $dates[1]->format('Ymd'), $day['absolute_url']); | ||
417 | static::assertCount(2, $day['links']); | ||
418 | |||
419 | static::assertSame(2, $day['links'][0]['id']); | ||
420 | static::assertSame('http://domain.tld/2', $day['links'][0]['url']); | ||
421 | static::assertEquals($dates[1], $day['links'][0]['created']); | ||
422 | static::assertSame(3, $day['links'][1]['id']); | ||
423 | static::assertSame('http://domain.tld/3', $day['links'][1]['url']); | ||
424 | static::assertEquals($dates[1], $day['links'][1]['created']); | ||
425 | } | ||
426 | |||
427 | /** | ||
428 | * Daily RSS - trigger cache rendering | ||
429 | */ | ||
430 | public function testValidRssControllerInvokeTriggerCache(): void | ||
431 | { | ||
432 | $request = $this->createMock(Request::class); | ||
433 | $response = new Response(); | ||
434 | |||
435 | $this->container->pageCacheManager->method('getCachePage')->willReturnCallback(function (): CachedPage { | ||
436 | $cachedPage = $this->createMock(CachedPage::class); | ||
437 | $cachedPage->method('cachedVersion')->willReturn('this is cache!'); | ||
438 | |||
439 | return $cachedPage; | ||
440 | }); | ||
441 | |||
442 | $this->container->bookmarkService->expects(static::never())->method('search'); | ||
443 | |||
444 | $result = $this->controller->rss($request, $response); | ||
445 | |||
446 | static::assertSame(200, $result->getStatusCode()); | ||
447 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
448 | static::assertSame('this is cache!', (string) $result->getBody()); | ||
449 | } | ||
450 | |||
451 | /** | ||
452 | * Daily RSS - No bookmark | ||
453 | */ | ||
454 | public function testValidRssControllerInvokeNoBookmark(): void | ||
455 | { | ||
456 | $request = $this->createMock(Request::class); | ||
457 | $response = new Response(); | ||
458 | |||
459 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([]); | ||
460 | |||
461 | // Save RainTPL assigned variables | ||
462 | $assignedVariables = []; | ||
463 | $this->assignTemplateVars($assignedVariables); | ||
464 | |||
465 | $result = $this->controller->rss($request, $response); | ||
466 | |||
467 | static::assertSame(200, $result->getStatusCode()); | ||
468 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
469 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
470 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
471 | static::assertSame('http://shaarli', $assignedVariables['index_url']); | ||
472 | static::assertSame('http://shaarli/daily-rss', $assignedVariables['page_url']); | ||
473 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
474 | static::assertCount(0, $assignedVariables['days']); | ||
475 | } | ||
476 | } | ||
diff --git a/tests/front/controller/visitor/ErrorControllerTest.php b/tests/front/controller/visitor/ErrorControllerTest.php new file mode 100644 index 00000000..e497bfef --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Front\Exception\ShaarliFrontException; | ||
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/FeedControllerTest.php b/tests/front/controller/visitor/FeedControllerTest.php new file mode 100644 index 00000000..fb417e2a --- /dev/null +++ b/tests/front/controller/visitor/FeedControllerTest.php | |||
@@ -0,0 +1,145 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Feed\FeedBuilder; | ||
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::at(0)) | ||
49 | ->method('executeHooks') | ||
50 | ->willReturnCallback(function (string $hook, array $data, array $param): void { | ||
51 | static::assertSame('render_feed', $hook); | ||
52 | static::assertSame('data', $data['content']); | ||
53 | |||
54 | static::assertArrayHasKey('loggedin', $param); | ||
55 | static::assertSame('rss', $param['target']); | ||
56 | }) | ||
57 | ; | ||
58 | |||
59 | $result = $this->controller->rss($request, $response); | ||
60 | |||
61 | static::assertSame(200, $result->getStatusCode()); | ||
62 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
63 | static::assertSame('feed.rss', (string) $result->getBody()); | ||
64 | static::assertSame('data', $assignedVariables['content']); | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * Feed Controller - ATOM default behaviour | ||
69 | */ | ||
70 | public function testDefaultAtomController(): void | ||
71 | { | ||
72 | $request = $this->createMock(Request::class); | ||
73 | $response = new Response(); | ||
74 | |||
75 | $this->container->feedBuilder->expects(static::once())->method('setLocale'); | ||
76 | $this->container->feedBuilder->expects(static::once())->method('setHideDates')->with(false); | ||
77 | $this->container->feedBuilder->expects(static::once())->method('setUsePermalinks')->with(true); | ||
78 | |||
79 | // Save RainTPL assigned variables | ||
80 | $assignedVariables = []; | ||
81 | $this->assignTemplateVars($assignedVariables); | ||
82 | |||
83 | $this->container->feedBuilder->method('buildData')->willReturn(['content' => 'data']); | ||
84 | |||
85 | // Make sure that PluginManager hook is triggered | ||
86 | $this->container->pluginManager | ||
87 | ->expects(static::at(0)) | ||
88 | ->method('executeHooks') | ||
89 | ->willReturnCallback(function (string $hook, array $data, array $param): void { | ||
90 | static::assertSame('render_feed', $hook); | ||
91 | static::assertSame('data', $data['content']); | ||
92 | |||
93 | static::assertArrayHasKey('loggedin', $param); | ||
94 | static::assertSame('atom', $param['target']); | ||
95 | }) | ||
96 | ; | ||
97 | |||
98 | $result = $this->controller->atom($request, $response); | ||
99 | |||
100 | static::assertSame(200, $result->getStatusCode()); | ||
101 | static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]); | ||
102 | static::assertSame('feed.atom', (string) $result->getBody()); | ||
103 | static::assertSame('data', $assignedVariables['content']); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Feed Controller - ATOM with parameters | ||
108 | */ | ||
109 | public function testAtomControllerWithParameters(): void | ||
110 | { | ||
111 | $request = $this->createMock(Request::class); | ||
112 | $request->method('getParams')->willReturn(['parameter' => 'value']); | ||
113 | $response = new Response(); | ||
114 | |||
115 | // Save RainTPL assigned variables | ||
116 | $assignedVariables = []; | ||
117 | $this->assignTemplateVars($assignedVariables); | ||
118 | |||
119 | $this->container->feedBuilder | ||
120 | ->method('buildData') | ||
121 | ->with('atom', ['parameter' => 'value']) | ||
122 | ->willReturn(['content' => 'data']) | ||
123 | ; | ||
124 | |||
125 | // Make sure that PluginManager hook is triggered | ||
126 | $this->container->pluginManager | ||
127 | ->expects(static::at(0)) | ||
128 | ->method('executeHooks') | ||
129 | ->willReturnCallback(function (string $hook, array $data, array $param): void { | ||
130 | static::assertSame('render_feed', $hook); | ||
131 | static::assertSame('data', $data['content']); | ||
132 | |||
133 | static::assertArrayHasKey('loggedin', $param); | ||
134 | static::assertSame('atom', $param['target']); | ||
135 | }) | ||
136 | ; | ||
137 | |||
138 | $result = $this->controller->atom($request, $response); | ||
139 | |||
140 | static::assertSame(200, $result->getStatusCode()); | ||
141 | static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]); | ||
142 | static::assertSame('feed.atom', (string) $result->getBody()); | ||
143 | static::assertSame('data', $assignedVariables['content']); | ||
144 | } | ||
145 | } | ||
diff --git a/tests/front/controller/visitor/FrontControllerMockHelper.php b/tests/front/controller/visitor/FrontControllerMockHelper.php new file mode 100644 index 00000000..e0bd4ecf --- /dev/null +++ b/tests/front/controller/visitor/FrontControllerMockHelper.php | |||
@@ -0,0 +1,119 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use PHPUnit\Framework\MockObject\MockObject; | ||
8 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Container\ShaarliTestContainer; | ||
11 | use Shaarli\Formatter\BookmarkFormatter; | ||
12 | use Shaarli\Formatter\BookmarkRawFormatter; | ||
13 | use Shaarli\Formatter\FormatterFactory; | ||
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 | |||
20 | /** | ||
21 | * Trait FrontControllerMockHelper | ||
22 | * | ||
23 | * Helper trait used to initialize the ShaarliContainer and mock its services for controller tests. | ||
24 | * | ||
25 | * @property ShaarliTestContainer $container | ||
26 | * @package Shaarli\Front\Controller | ||
27 | */ | ||
28 | trait FrontControllerMockHelper | ||
29 | { | ||
30 | /** @var ShaarliTestContainer */ | ||
31 | protected $container; | ||
32 | |||
33 | /** | ||
34 | * Mock the container instance and initialize container's services used by tests | ||
35 | */ | ||
36 | protected function createContainer(): void | ||
37 | { | ||
38 | $this->container = $this->createMock(ShaarliTestContainer::class); | ||
39 | |||
40 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
41 | |||
42 | // Config | ||
43 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
44 | $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
45 | return $default === null ? $parameter : $default; | ||
46 | }); | ||
47 | |||
48 | // PageBuilder | ||
49 | $this->container->pageBuilder = $this->createMock(PageBuilder::class); | ||
50 | $this->container->pageBuilder | ||
51 | ->method('render') | ||
52 | ->willReturnCallback(function (string $template): string { | ||
53 | return $template; | ||
54 | }) | ||
55 | ; | ||
56 | |||
57 | // Plugin Manager | ||
58 | $this->container->pluginManager = $this->createMock(PluginManager::class); | ||
59 | |||
60 | // BookmarkService | ||
61 | $this->container->bookmarkService = $this->createMock(BookmarkServiceInterface::class); | ||
62 | |||
63 | // Formatter | ||
64 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
65 | $this->container->formatterFactory | ||
66 | ->method('getFormatter') | ||
67 | ->willReturnCallback(function (): BookmarkFormatter { | ||
68 | return new BookmarkRawFormatter($this->container->conf, true); | ||
69 | }) | ||
70 | ; | ||
71 | |||
72 | // CacheManager | ||
73 | $this->container->pageCacheManager = $this->createMock(PageCacheManager::class); | ||
74 | |||
75 | // SessionManager | ||
76 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
77 | |||
78 | // $_SERVER | ||
79 | $this->container->environment = [ | ||
80 | 'SERVER_NAME' => 'shaarli', | ||
81 | 'SERVER_PORT' => '80', | ||
82 | 'REQUEST_URI' => '/daily-rss', | ||
83 | 'REMOTE_ADDR' => '1.2.3.4', | ||
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 | ->expects(static::atLeastOnce()) | ||
98 | ->method('assign') | ||
99 | ->willReturnCallback(function ($key, $value) use (&$variables) { | ||
100 | $variables[$key] = $value; | ||
101 | |||
102 | return $this; | ||
103 | }) | ||
104 | ; | ||
105 | } | ||
106 | |||
107 | protected static function generateString(int $length): string | ||
108 | { | ||
109 | // bin2hex(random_bytes) generates string twice as long as given parameter | ||
110 | $length = (int) ceil($length / 2); | ||
111 | |||
112 | return bin2hex(random_bytes($length)); | ||
113 | } | ||
114 | |||
115 | /** | ||
116 | * Force to be used in PHPUnit context. | ||
117 | */ | ||
118 | protected abstract function createMock($originalClassName): MockObject; | ||
119 | } | ||
diff --git a/tests/front/controller/visitor/InstallControllerTest.php b/tests/front/controller/visitor/InstallControllerTest.php new file mode 100644 index 00000000..3b855365 --- /dev/null +++ b/tests/front/controller/visitor/InstallControllerTest.php | |||
@@ -0,0 +1,262 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Exception\AlreadyInstalledException; | ||
10 | use Shaarli\Security\SessionManager; | ||
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', $confSettings['general.title']); | ||
261 | } | ||
262 | } | ||
diff --git a/tests/front/controller/visitor/LoginControllerTest.php b/tests/front/controller/visitor/LoginControllerTest.php new file mode 100644 index 00000000..0a21f938 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Exception\LoginBannedException; | ||
10 | use Shaarli\Front\Exception\WrongTokenException; | ||
11 | use Shaarli\Render\TemplatePage; | ||
12 | use Shaarli\Security\CookieManager; | ||
13 | use Shaarli\Security\SessionManager; | ||
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..5f9f5b12 --- /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 PHPUnit\Framework\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', $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..3dc3f292 --- /dev/null +++ b/tests/front/controller/visitor/PictureWallControllerTest.php | |||
@@ -0,0 +1,121 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Front\Exception\ThumbnailsDisabledException; | ||
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::at(0)) | ||
71 | ->method('executeHooks') | ||
72 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
73 | static::assertSame('render_picwall', $hook); | ||
74 | static::assertArrayHasKey('linksToDisplay', $data); | ||
75 | static::assertCount(2, $data['linksToDisplay']); | ||
76 | static::assertSame(1, $data['linksToDisplay'][0]['id']); | ||
77 | static::assertSame(3, $data['linksToDisplay'][1]['id']); | ||
78 | static::assertArrayHasKey('loggedin', $param); | ||
79 | |||
80 | return $data; | ||
81 | }); | ||
82 | |||
83 | $result = $this->controller->index($request, $response); | ||
84 | |||
85 | static::assertSame(200, $result->getStatusCode()); | ||
86 | static::assertSame('picwall', (string) $result->getBody()); | ||
87 | static::assertSame('Picture wall - Shaarli', $assignedVariables['pagetitle']); | ||
88 | static::assertCount(2, $assignedVariables['linksToDisplay']); | ||
89 | |||
90 | $link = $assignedVariables['linksToDisplay'][0]; | ||
91 | |||
92 | static::assertSame(1, $link['id']); | ||
93 | static::assertSame('http://url.tld', $link['url']); | ||
94 | static::assertSame('thumb1', $link['thumbnail']); | ||
95 | |||
96 | $link = $assignedVariables['linksToDisplay'][1]; | ||
97 | |||
98 | static::assertSame(3, $link['id']); | ||
99 | static::assertSame('http://url3.tld', $link['url']); | ||
100 | static::assertSame('thumb2', $link['thumbnail']); | ||
101 | } | ||
102 | |||
103 | public function testControllerWithThumbnailsDisabled(): void | ||
104 | { | ||
105 | $this->expectException(ThumbnailsDisabledException::class); | ||
106 | |||
107 | $request = $this->createMock(Request::class); | ||
108 | $response = new Response(); | ||
109 | |||
110 | // ConfigManager: thumbnails are disabled | ||
111 | $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
112 | if ($parameter === 'thumbnails.mode') { | ||
113 | return Thumbnailer::MODE_NONE; | ||
114 | } | ||
115 | |||
116 | return $default; | ||
117 | }); | ||
118 | |||
119 | $this->controller->index($request, $response); | ||
120 | } | ||
121 | } | ||
diff --git a/tests/front/controller/visitor/PublicSessionFilterControllerTest.php b/tests/front/controller/visitor/PublicSessionFilterControllerTest.php new file mode 100644 index 00000000..06352750 --- /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 PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Security\SessionManager; | ||
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..316ce49c --- /dev/null +++ b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php | |||
@@ -0,0 +1,215 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\BookmarkFilter; | ||
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.tld/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.tld/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.tld/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.tld/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.tld/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.tld/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.tld/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 | } | ||
diff --git a/tests/front/controller/visitor/TagCloudControllerTest.php b/tests/front/controller/visitor/TagCloudControllerTest.php new file mode 100644 index 00000000..9a6a4bc0 --- /dev/null +++ b/tests/front/controller/visitor/TagCloudControllerTest.php | |||
@@ -0,0 +1,369 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Bookmark\BookmarkFilter; | ||
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::at(0)) | ||
57 | ->method('executeHooks') | ||
58 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
59 | static::assertSame('render_tagcloud', $hook); | ||
60 | static::assertSame('', $data['search_tags']); | ||
61 | static::assertCount(3, $data['tags']); | ||
62 | |||
63 | static::assertArrayHasKey('loggedin', $param); | ||
64 | |||
65 | return $data; | ||
66 | }) | ||
67 | ; | ||
68 | |||
69 | $result = $this->controller->cloud($request, $response); | ||
70 | |||
71 | static::assertSame(200, $result->getStatusCode()); | ||
72 | static::assertSame('tag.cloud', (string) $result->getBody()); | ||
73 | static::assertSame('Tag cloud - Shaarli', $assignedVariables['pagetitle']); | ||
74 | |||
75 | static::assertSame('', $assignedVariables['search_tags']); | ||
76 | static::assertCount(3, $assignedVariables['tags']); | ||
77 | static::assertSame($expectedOrder, array_keys($assignedVariables['tags'])); | ||
78 | |||
79 | foreach ($allTags as $tag => $count) { | ||
80 | static::assertArrayHasKey($tag, $assignedVariables['tags']); | ||
81 | static::assertSame($count, $assignedVariables['tags'][$tag]['count']); | ||
82 | static::assertGreaterThan(0, $assignedVariables['tags'][$tag]['size']); | ||
83 | static::assertLessThan(5, $assignedVariables['tags'][$tag]['size']); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * Tag Cloud - Additional parameters: | ||
89 | * - logged in | ||
90 | * - visibility private | ||
91 | * - search tags: `ghi` and `def` (note that filtered tags are not displayed anymore) | ||
92 | */ | ||
93 | public function testValidCloudControllerInvokeWithParameters(): void | ||
94 | { | ||
95 | $request = $this->createMock(Request::class); | ||
96 | $request | ||
97 | ->method('getQueryParam') | ||
98 | ->with() | ||
99 | ->willReturnCallback(function (string $key): ?string { | ||
100 | if ('searchtags' === $key) { | ||
101 | return 'ghi def'; | ||
102 | } | ||
103 | |||
104 | return null; | ||
105 | }) | ||
106 | ; | ||
107 | $response = new Response(); | ||
108 | |||
109 | // Save RainTPL assigned variables | ||
110 | $assignedVariables = []; | ||
111 | $this->assignTemplateVars($assignedVariables); | ||
112 | |||
113 | $this->container->loginManager->method('isLoggedin')->willReturn(true); | ||
114 | $this->container->sessionManager->expects(static::once())->method('getSessionParameter')->willReturn('private'); | ||
115 | |||
116 | $this->container->bookmarkService | ||
117 | ->expects(static::once()) | ||
118 | ->method('bookmarksCountPerTag') | ||
119 | ->with(['ghi', 'def'], BookmarkFilter::$PRIVATE) | ||
120 | ->willReturnCallback(function (): array { | ||
121 | return ['abc' => 3]; | ||
122 | }) | ||
123 | ; | ||
124 | |||
125 | // Make sure that PluginManager hook is triggered | ||
126 | $this->container->pluginManager | ||
127 | ->expects(static::at(0)) | ||
128 | ->method('executeHooks') | ||
129 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
130 | static::assertSame('render_tagcloud', $hook); | ||
131 | static::assertSame('ghi def', $data['search_tags']); | ||
132 | static::assertCount(1, $data['tags']); | ||
133 | |||
134 | static::assertArrayHasKey('loggedin', $param); | ||
135 | |||
136 | return $data; | ||
137 | }) | ||
138 | ; | ||
139 | |||
140 | $result = $this->controller->cloud($request, $response); | ||
141 | |||
142 | static::assertSame(200, $result->getStatusCode()); | ||
143 | static::assertSame('tag.cloud', (string) $result->getBody()); | ||
144 | static::assertSame('ghi def - Tag cloud - Shaarli', $assignedVariables['pagetitle']); | ||
145 | |||
146 | static::assertSame('ghi def', $assignedVariables['search_tags']); | ||
147 | static::assertCount(1, $assignedVariables['tags']); | ||
148 | |||
149 | static::assertArrayHasKey('abc', $assignedVariables['tags']); | ||
150 | static::assertSame(3, $assignedVariables['tags']['abc']['count']); | ||
151 | static::assertGreaterThan(0, $assignedVariables['tags']['abc']['size']); | ||
152 | static::assertLessThan(5, $assignedVariables['tags']['abc']['size']); | ||
153 | } | ||
154 | |||
155 | /** | ||
156 | * Tag Cloud - empty | ||
157 | */ | ||
158 | public function testEmptyCloud(): void | ||
159 | { | ||
160 | $request = $this->createMock(Request::class); | ||
161 | $response = new Response(); | ||
162 | |||
163 | // Save RainTPL assigned variables | ||
164 | $assignedVariables = []; | ||
165 | $this->assignTemplateVars($assignedVariables); | ||
166 | |||
167 | $this->container->bookmarkService | ||
168 | ->expects(static::once()) | ||
169 | ->method('bookmarksCountPerTag') | ||
170 | ->with([], null) | ||
171 | ->willReturnCallback(function (array $parameters, ?string $visibility): array { | ||
172 | return []; | ||
173 | }) | ||
174 | ; | ||
175 | |||
176 | // Make sure that PluginManager hook is triggered | ||
177 | $this->container->pluginManager | ||
178 | ->expects(static::at(0)) | ||
179 | ->method('executeHooks') | ||
180 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
181 | static::assertSame('render_tagcloud', $hook); | ||
182 | static::assertSame('', $data['search_tags']); | ||
183 | static::assertCount(0, $data['tags']); | ||
184 | |||
185 | static::assertArrayHasKey('loggedin', $param); | ||
186 | |||
187 | return $data; | ||
188 | }) | ||
189 | ; | ||
190 | |||
191 | $result = $this->controller->cloud($request, $response); | ||
192 | |||
193 | static::assertSame(200, $result->getStatusCode()); | ||
194 | static::assertSame('tag.cloud', (string) $result->getBody()); | ||
195 | static::assertSame('Tag cloud - Shaarli', $assignedVariables['pagetitle']); | ||
196 | |||
197 | static::assertSame('', $assignedVariables['search_tags']); | ||
198 | static::assertCount(0, $assignedVariables['tags']); | ||
199 | } | ||
200 | |||
201 | /** | ||
202 | * Tag List - Default sort is by usage DESC | ||
203 | */ | ||
204 | public function testValidListControllerInvokeDefault(): void | ||
205 | { | ||
206 | $allTags = [ | ||
207 | 'def' => 12, | ||
208 | 'abc' => 3, | ||
209 | 'ghi' => 1, | ||
210 | ]; | ||
211 | |||
212 | $request = $this->createMock(Request::class); | ||
213 | $response = new Response(); | ||
214 | |||
215 | // Save RainTPL assigned variables | ||
216 | $assignedVariables = []; | ||
217 | $this->assignTemplateVars($assignedVariables); | ||
218 | |||
219 | $this->container->bookmarkService | ||
220 | ->expects(static::once()) | ||
221 | ->method('bookmarksCountPerTag') | ||
222 | ->with([], null) | ||
223 | ->willReturnCallback(function () use ($allTags): array { | ||
224 | return $allTags; | ||
225 | }) | ||
226 | ; | ||
227 | |||
228 | // Make sure that PluginManager hook is triggered | ||
229 | $this->container->pluginManager | ||
230 | ->expects(static::at(0)) | ||
231 | ->method('executeHooks') | ||
232 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
233 | static::assertSame('render_taglist', $hook); | ||
234 | static::assertSame('', $data['search_tags']); | ||
235 | static::assertCount(3, $data['tags']); | ||
236 | |||
237 | static::assertArrayHasKey('loggedin', $param); | ||
238 | |||
239 | return $data; | ||
240 | }) | ||
241 | ; | ||
242 | |||
243 | $result = $this->controller->list($request, $response); | ||
244 | |||
245 | static::assertSame(200, $result->getStatusCode()); | ||
246 | static::assertSame('tag.list', (string) $result->getBody()); | ||
247 | static::assertSame('Tag list - Shaarli', $assignedVariables['pagetitle']); | ||
248 | |||
249 | static::assertSame('', $assignedVariables['search_tags']); | ||
250 | static::assertCount(3, $assignedVariables['tags']); | ||
251 | |||
252 | foreach ($allTags as $tag => $count) { | ||
253 | static::assertSame($count, $assignedVariables['tags'][$tag]); | ||
254 | } | ||
255 | } | ||
256 | |||
257 | /** | ||
258 | * Tag List - Additional parameters: | ||
259 | * - logged in | ||
260 | * - visibility private | ||
261 | * - search tags: `ghi` and `def` (note that filtered tags are not displayed anymore) | ||
262 | * - sort alphabetically | ||
263 | */ | ||
264 | public function testValidListControllerInvokeWithParameters(): void | ||
265 | { | ||
266 | $request = $this->createMock(Request::class); | ||
267 | $request | ||
268 | ->method('getQueryParam') | ||
269 | ->with() | ||
270 | ->willReturnCallback(function (string $key): ?string { | ||
271 | if ('searchtags' === $key) { | ||
272 | return 'ghi def'; | ||
273 | } elseif ('sort' === $key) { | ||
274 | return 'alpha'; | ||
275 | } | ||
276 | |||
277 | return null; | ||
278 | }) | ||
279 | ; | ||
280 | $response = new Response(); | ||
281 | |||
282 | // Save RainTPL assigned variables | ||
283 | $assignedVariables = []; | ||
284 | $this->assignTemplateVars($assignedVariables); | ||
285 | |||
286 | $this->container->loginManager->method('isLoggedin')->willReturn(true); | ||
287 | $this->container->sessionManager->expects(static::once())->method('getSessionParameter')->willReturn('private'); | ||
288 | |||
289 | $this->container->bookmarkService | ||
290 | ->expects(static::once()) | ||
291 | ->method('bookmarksCountPerTag') | ||
292 | ->with(['ghi', 'def'], BookmarkFilter::$PRIVATE) | ||
293 | ->willReturnCallback(function (): array { | ||
294 | return ['abc' => 3]; | ||
295 | }) | ||
296 | ; | ||
297 | |||
298 | // Make sure that PluginManager hook is triggered | ||
299 | $this->container->pluginManager | ||
300 | ->expects(static::at(0)) | ||
301 | ->method('executeHooks') | ||
302 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
303 | static::assertSame('render_taglist', $hook); | ||
304 | static::assertSame('ghi def', $data['search_tags']); | ||
305 | static::assertCount(1, $data['tags']); | ||
306 | |||
307 | static::assertArrayHasKey('loggedin', $param); | ||
308 | |||
309 | return $data; | ||
310 | }) | ||
311 | ; | ||
312 | |||
313 | $result = $this->controller->list($request, $response); | ||
314 | |||
315 | static::assertSame(200, $result->getStatusCode()); | ||
316 | static::assertSame('tag.list', (string) $result->getBody()); | ||
317 | static::assertSame('ghi def - Tag list - Shaarli', $assignedVariables['pagetitle']); | ||
318 | |||
319 | static::assertSame('ghi def', $assignedVariables['search_tags']); | ||
320 | static::assertCount(1, $assignedVariables['tags']); | ||
321 | static::assertSame(3, $assignedVariables['tags']['abc']); | ||
322 | } | ||
323 | |||
324 | /** | ||
325 | * Tag List - empty | ||
326 | */ | ||
327 | public function testEmptyList(): void | ||
328 | { | ||
329 | $request = $this->createMock(Request::class); | ||
330 | $response = new Response(); | ||
331 | |||
332 | // Save RainTPL assigned variables | ||
333 | $assignedVariables = []; | ||
334 | $this->assignTemplateVars($assignedVariables); | ||
335 | |||
336 | $this->container->bookmarkService | ||
337 | ->expects(static::once()) | ||
338 | ->method('bookmarksCountPerTag') | ||
339 | ->with([], null) | ||
340 | ->willReturnCallback(function (array $parameters, ?string $visibility): array { | ||
341 | return []; | ||
342 | }) | ||
343 | ; | ||
344 | |||
345 | // Make sure that PluginManager hook is triggered | ||
346 | $this->container->pluginManager | ||
347 | ->expects(static::at(0)) | ||
348 | ->method('executeHooks') | ||
349 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
350 | static::assertSame('render_taglist', $hook); | ||
351 | static::assertSame('', $data['search_tags']); | ||
352 | static::assertCount(0, $data['tags']); | ||
353 | |||
354 | static::assertArrayHasKey('loggedin', $param); | ||
355 | |||
356 | return $data; | ||
357 | }) | ||
358 | ; | ||
359 | |||
360 | $result = $this->controller->list($request, $response); | ||
361 | |||
362 | static::assertSame(200, $result->getStatusCode()); | ||
363 | static::assertSame('tag.list', (string) $result->getBody()); | ||
364 | static::assertSame('Tag list - Shaarli', $assignedVariables['pagetitle']); | ||
365 | |||
366 | static::assertSame('', $assignedVariables['search_tags']); | ||
367 | static::assertCount(0, $assignedVariables['tags']); | ||
368 | } | ||
369 | } | ||
diff --git a/tests/front/controller/visitor/TagControllerTest.php b/tests/front/controller/visitor/TagControllerTest.php new file mode 100644 index 00000000..43076086 --- /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 PHPUnit\Framework\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/IndexUrlTest.php b/tests/http/HttpUtils/IndexUrlTest.php index bcbe59cb..73d33cd4 100644 --- a/tests/http/HttpUtils/IndexUrlTest.php +++ b/tests/http/HttpUtils/IndexUrlTest.php | |||
@@ -71,4 +71,36 @@ class IndexUrlTest extends \PHPUnit\Framework\TestCase | |||
71 | ) | 71 | ) |
72 | ); | 72 | ); |
73 | } | 73 | } |
74 | |||
75 | /** | ||
76 | * The route is stored in REQUEST_URI | ||
77 | */ | ||
78 | public function testPageUrlWithRoute() | ||
79 | { | ||
80 | $this->assertEquals( | ||
81 | 'http://host.tld/picture-wall', | ||
82 | page_url( | ||
83 | array( | ||
84 | 'HTTPS' => 'Off', | ||
85 | 'SERVER_NAME' => 'host.tld', | ||
86 | 'SERVER_PORT' => '80', | ||
87 | 'SCRIPT_NAME' => '/index.php', | ||
88 | 'REQUEST_URI' => '/picture-wall', | ||
89 | ) | ||
90 | ) | ||
91 | ); | ||
92 | |||
93 | $this->assertEquals( | ||
94 | 'http://host.tld/admin/picture-wall', | ||
95 | page_url( | ||
96 | array( | ||
97 | 'HTTPS' => 'Off', | ||
98 | 'SERVER_NAME' => 'host.tld', | ||
99 | 'SERVER_PORT' => '80', | ||
100 | 'SCRIPT_NAME' => '/admin/index.php', | ||
101 | 'REQUEST_URI' => '/admin/picture-wall', | ||
102 | ) | ||
103 | ) | ||
104 | ); | ||
105 | } | ||
74 | } | 106 | } |
diff --git a/tests/legacy/LegacyControllerTest.php b/tests/legacy/LegacyControllerTest.php new file mode 100644 index 00000000..759a5b2a --- /dev/null +++ b/tests/legacy/LegacyControllerTest.php | |||
@@ -0,0 +1,99 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Legacy; | ||
6 | |||
7 | use PHPUnit\Framework\TestCase; | ||
8 | use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper; | ||
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', false], | ||
70 | ['post', ['title' => 'test'], '/admin/shaare?title=test', true], | ||
71 | ['post', ['title' => 'test'], '/login?title=test', false], | ||
72 | ['addlink', [], '/admin/add-shaare', true], | ||
73 | ['addlink', [], '/login', 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 | ]; | ||
98 | } | ||
99 | } | ||
diff --git a/tests/legacy/LegacyLinkDBTest.php b/tests/legacy/LegacyLinkDBTest.php index 17b2b0e6..0884ad03 100644 --- a/tests/legacy/LegacyLinkDBTest.php +++ b/tests/legacy/LegacyLinkDBTest.php | |||
@@ -11,7 +11,6 @@ use ReflectionClass; | |||
11 | use Shaarli; | 11 | use Shaarli; |
12 | use Shaarli\Bookmark\Bookmark; | 12 | use Shaarli\Bookmark\Bookmark; |
13 | 13 | ||
14 | require_once 'application/feed/Cache.php'; | ||
15 | require_once 'application/Utils.php'; | 14 | require_once 'application/Utils.php'; |
16 | require_once 'tests/utils/ReferenceLinkDB.php'; | 15 | require_once 'tests/utils/ReferenceLinkDB.php'; |
17 | 16 | ||
diff --git a/tests/RouterTest.php b/tests/legacy/LegacyRouterTest.php index 0cd49bb8..c2019ca7 100644 --- a/tests/RouterTest.php +++ b/tests/legacy/LegacyRouterTest.php | |||
@@ -1,10 +1,13 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli; | 2 | |
3 | namespace Shaarli\Legacy; | ||
4 | |||
5 | use PHPUnit\Framework\TestCase; | ||
3 | 6 | ||
4 | /** | 7 | /** |
5 | * Unit tests for Router | 8 | * Unit tests for Router |
6 | */ | 9 | */ |
7 | class RouterTest extends \PHPUnit\Framework\TestCase | 10 | class LegacyRouterTest extends TestCase |
8 | { | 11 | { |
9 | /** | 12 | /** |
10 | * Test findPage: login page output. | 13 | * Test findPage: login page output. |
@@ -15,18 +18,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
15 | public function testFindPageLoginValid() | 18 | public function testFindPageLoginValid() |
16 | { | 19 | { |
17 | $this->assertEquals( | 20 | $this->assertEquals( |
18 | Router::$PAGE_LOGIN, | 21 | LegacyRouter::$PAGE_LOGIN, |
19 | Router::findPage('do=login', array(), false) | 22 | LegacyRouter::findPage('do=login', array(), false) |
20 | ); | 23 | ); |
21 | 24 | ||
22 | $this->assertEquals( | 25 | $this->assertEquals( |
23 | Router::$PAGE_LOGIN, | 26 | LegacyRouter::$PAGE_LOGIN, |
24 | Router::findPage('do=login', array(), 1) | 27 | LegacyRouter::findPage('do=login', array(), 1) |
25 | ); | 28 | ); |
26 | 29 | ||
27 | $this->assertEquals( | 30 | $this->assertEquals( |
28 | Router::$PAGE_LOGIN, | 31 | LegacyRouter::$PAGE_LOGIN, |
29 | Router::findPage('do=login&stuff', array(), false) | 32 | LegacyRouter::findPage('do=login&stuff', array(), false) |
30 | ); | 33 | ); |
31 | } | 34 | } |
32 | 35 | ||
@@ -39,13 +42,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
39 | public function testFindPageLoginInvalid() | 42 | public function testFindPageLoginInvalid() |
40 | { | 43 | { |
41 | $this->assertNotEquals( | 44 | $this->assertNotEquals( |
42 | Router::$PAGE_LOGIN, | 45 | LegacyRouter::$PAGE_LOGIN, |
43 | Router::findPage('do=login', array(), true) | 46 | LegacyRouter::findPage('do=login', array(), true) |
44 | ); | 47 | ); |
45 | 48 | ||
46 | $this->assertNotEquals( | 49 | $this->assertNotEquals( |
47 | Router::$PAGE_LOGIN, | 50 | LegacyRouter::$PAGE_LOGIN, |
48 | Router::findPage('do=other', array(), false) | 51 | LegacyRouter::findPage('do=other', array(), false) |
49 | ); | 52 | ); |
50 | } | 53 | } |
51 | 54 | ||
@@ -58,13 +61,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
58 | public function testFindPagePicwallValid() | 61 | public function testFindPagePicwallValid() |
59 | { | 62 | { |
60 | $this->assertEquals( | 63 | $this->assertEquals( |
61 | Router::$PAGE_PICWALL, | 64 | LegacyRouter::$PAGE_PICWALL, |
62 | Router::findPage('do=picwall', array(), false) | 65 | LegacyRouter::findPage('do=picwall', array(), false) |
63 | ); | 66 | ); |
64 | 67 | ||
65 | $this->assertEquals( | 68 | $this->assertEquals( |
66 | Router::$PAGE_PICWALL, | 69 | LegacyRouter::$PAGE_PICWALL, |
67 | Router::findPage('do=picwall', array(), true) | 70 | LegacyRouter::findPage('do=picwall', array(), true) |
68 | ); | 71 | ); |
69 | } | 72 | } |
70 | 73 | ||
@@ -77,13 +80,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
77 | public function testFindPagePicwallInvalid() | 80 | public function testFindPagePicwallInvalid() |
78 | { | 81 | { |
79 | $this->assertEquals( | 82 | $this->assertEquals( |
80 | Router::$PAGE_PICWALL, | 83 | LegacyRouter::$PAGE_PICWALL, |
81 | Router::findPage('do=picwall&stuff', array(), false) | 84 | LegacyRouter::findPage('do=picwall&stuff', array(), false) |
82 | ); | 85 | ); |
83 | 86 | ||
84 | $this->assertNotEquals( | 87 | $this->assertNotEquals( |
85 | Router::$PAGE_PICWALL, | 88 | LegacyRouter::$PAGE_PICWALL, |
86 | Router::findPage('do=other', array(), false) | 89 | LegacyRouter::findPage('do=other', array(), false) |
87 | ); | 90 | ); |
88 | } | 91 | } |
89 | 92 | ||
@@ -96,18 +99,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
96 | public function testFindPageTagcloudValid() | 99 | public function testFindPageTagcloudValid() |
97 | { | 100 | { |
98 | $this->assertEquals( | 101 | $this->assertEquals( |
99 | Router::$PAGE_TAGCLOUD, | 102 | LegacyRouter::$PAGE_TAGCLOUD, |
100 | Router::findPage('do=tagcloud', array(), false) | 103 | LegacyRouter::findPage('do=tagcloud', array(), false) |
101 | ); | 104 | ); |
102 | 105 | ||
103 | $this->assertEquals( | 106 | $this->assertEquals( |
104 | Router::$PAGE_TAGCLOUD, | 107 | LegacyRouter::$PAGE_TAGCLOUD, |
105 | Router::findPage('do=tagcloud', array(), true) | 108 | LegacyRouter::findPage('do=tagcloud', array(), true) |
106 | ); | 109 | ); |
107 | 110 | ||
108 | $this->assertEquals( | 111 | $this->assertEquals( |
109 | Router::$PAGE_TAGCLOUD, | 112 | LegacyRouter::$PAGE_TAGCLOUD, |
110 | Router::findPage('do=tagcloud&stuff', array(), false) | 113 | LegacyRouter::findPage('do=tagcloud&stuff', array(), false) |
111 | ); | 114 | ); |
112 | } | 115 | } |
113 | 116 | ||
@@ -120,8 +123,8 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
120 | public function testFindPageTagcloudInvalid() | 123 | public function testFindPageTagcloudInvalid() |
121 | { | 124 | { |
122 | $this->assertNotEquals( | 125 | $this->assertNotEquals( |
123 | Router::$PAGE_TAGCLOUD, | 126 | LegacyRouter::$PAGE_TAGCLOUD, |
124 | Router::findPage('do=other', array(), false) | 127 | LegacyRouter::findPage('do=other', array(), false) |
125 | ); | 128 | ); |
126 | } | 129 | } |
127 | 130 | ||
@@ -134,23 +137,23 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
134 | public function testFindPageLinklistValid() | 137 | public function testFindPageLinklistValid() |
135 | { | 138 | { |
136 | $this->assertEquals( | 139 | $this->assertEquals( |
137 | Router::$PAGE_LINKLIST, | 140 | LegacyRouter::$PAGE_LINKLIST, |
138 | Router::findPage('', array(), true) | 141 | LegacyRouter::findPage('', array(), true) |
139 | ); | 142 | ); |
140 | 143 | ||
141 | $this->assertEquals( | 144 | $this->assertEquals( |
142 | Router::$PAGE_LINKLIST, | 145 | LegacyRouter::$PAGE_LINKLIST, |
143 | Router::findPage('whatever', array(), true) | 146 | LegacyRouter::findPage('whatever', array(), true) |
144 | ); | 147 | ); |
145 | 148 | ||
146 | $this->assertEquals( | 149 | $this->assertEquals( |
147 | Router::$PAGE_LINKLIST, | 150 | LegacyRouter::$PAGE_LINKLIST, |
148 | Router::findPage('whatever', array(), false) | 151 | LegacyRouter::findPage('whatever', array(), false) |
149 | ); | 152 | ); |
150 | 153 | ||
151 | $this->assertEquals( | 154 | $this->assertEquals( |
152 | Router::$PAGE_LINKLIST, | 155 | LegacyRouter::$PAGE_LINKLIST, |
153 | Router::findPage('do=tools', array(), false) | 156 | LegacyRouter::findPage('do=tools', array(), false) |
154 | ); | 157 | ); |
155 | } | 158 | } |
156 | 159 | ||
@@ -163,13 +166,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
163 | public function testFindPageToolsValid() | 166 | public function testFindPageToolsValid() |
164 | { | 167 | { |
165 | $this->assertEquals( | 168 | $this->assertEquals( |
166 | Router::$PAGE_TOOLS, | 169 | LegacyRouter::$PAGE_TOOLS, |
167 | Router::findPage('do=tools', array(), true) | 170 | LegacyRouter::findPage('do=tools', array(), true) |
168 | ); | 171 | ); |
169 | 172 | ||
170 | $this->assertEquals( | 173 | $this->assertEquals( |
171 | Router::$PAGE_TOOLS, | 174 | LegacyRouter::$PAGE_TOOLS, |
172 | Router::findPage('do=tools&stuff', array(), true) | 175 | LegacyRouter::findPage('do=tools&stuff', array(), true) |
173 | ); | 176 | ); |
174 | } | 177 | } |
175 | 178 | ||
@@ -182,18 +185,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
182 | public function testFindPageToolsInvalid() | 185 | public function testFindPageToolsInvalid() |
183 | { | 186 | { |
184 | $this->assertNotEquals( | 187 | $this->assertNotEquals( |
185 | Router::$PAGE_TOOLS, | 188 | LegacyRouter::$PAGE_TOOLS, |
186 | Router::findPage('do=tools', array(), 1) | 189 | LegacyRouter::findPage('do=tools', array(), 1) |
187 | ); | 190 | ); |
188 | 191 | ||
189 | $this->assertNotEquals( | 192 | $this->assertNotEquals( |
190 | Router::$PAGE_TOOLS, | 193 | LegacyRouter::$PAGE_TOOLS, |
191 | Router::findPage('do=tools', array(), false) | 194 | LegacyRouter::findPage('do=tools', array(), false) |
192 | ); | 195 | ); |
193 | 196 | ||
194 | $this->assertNotEquals( | 197 | $this->assertNotEquals( |
195 | Router::$PAGE_TOOLS, | 198 | LegacyRouter::$PAGE_TOOLS, |
196 | Router::findPage('do=other', array(), true) | 199 | LegacyRouter::findPage('do=other', array(), true) |
197 | ); | 200 | ); |
198 | } | 201 | } |
199 | 202 | ||
@@ -206,12 +209,12 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
206 | public function testFindPageChangepasswdValid() | 209 | public function testFindPageChangepasswdValid() |
207 | { | 210 | { |
208 | $this->assertEquals( | 211 | $this->assertEquals( |
209 | Router::$PAGE_CHANGEPASSWORD, | 212 | LegacyRouter::$PAGE_CHANGEPASSWORD, |
210 | Router::findPage('do=changepasswd', array(), true) | 213 | LegacyRouter::findPage('do=changepasswd', array(), true) |
211 | ); | 214 | ); |
212 | $this->assertEquals( | 215 | $this->assertEquals( |
213 | Router::$PAGE_CHANGEPASSWORD, | 216 | LegacyRouter::$PAGE_CHANGEPASSWORD, |
214 | Router::findPage('do=changepasswd&stuff', array(), true) | 217 | LegacyRouter::findPage('do=changepasswd&stuff', array(), true) |
215 | ); | 218 | ); |
216 | } | 219 | } |
217 | 220 | ||
@@ -224,18 +227,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
224 | public function testFindPageChangepasswdInvalid() | 227 | public function testFindPageChangepasswdInvalid() |
225 | { | 228 | { |
226 | $this->assertNotEquals( | 229 | $this->assertNotEquals( |
227 | Router::$PAGE_CHANGEPASSWORD, | 230 | LegacyRouter::$PAGE_CHANGEPASSWORD, |
228 | Router::findPage('do=changepasswd', array(), 1) | 231 | LegacyRouter::findPage('do=changepasswd', array(), 1) |
229 | ); | 232 | ); |
230 | 233 | ||
231 | $this->assertNotEquals( | 234 | $this->assertNotEquals( |
232 | Router::$PAGE_CHANGEPASSWORD, | 235 | LegacyRouter::$PAGE_CHANGEPASSWORD, |
233 | Router::findPage('do=changepasswd', array(), false) | 236 | LegacyRouter::findPage('do=changepasswd', array(), false) |
234 | ); | 237 | ); |
235 | 238 | ||
236 | $this->assertNotEquals( | 239 | $this->assertNotEquals( |
237 | Router::$PAGE_CHANGEPASSWORD, | 240 | LegacyRouter::$PAGE_CHANGEPASSWORD, |
238 | Router::findPage('do=other', array(), true) | 241 | LegacyRouter::findPage('do=other', array(), true) |
239 | ); | 242 | ); |
240 | } | 243 | } |
241 | /** | 244 | /** |
@@ -247,13 +250,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
247 | public function testFindPageConfigureValid() | 250 | public function testFindPageConfigureValid() |
248 | { | 251 | { |
249 | $this->assertEquals( | 252 | $this->assertEquals( |
250 | Router::$PAGE_CONFIGURE, | 253 | LegacyRouter::$PAGE_CONFIGURE, |
251 | Router::findPage('do=configure', array(), true) | 254 | LegacyRouter::findPage('do=configure', array(), true) |
252 | ); | 255 | ); |
253 | 256 | ||
254 | $this->assertEquals( | 257 | $this->assertEquals( |
255 | Router::$PAGE_CONFIGURE, | 258 | LegacyRouter::$PAGE_CONFIGURE, |
256 | Router::findPage('do=configure&stuff', array(), true) | 259 | LegacyRouter::findPage('do=configure&stuff', array(), true) |
257 | ); | 260 | ); |
258 | } | 261 | } |
259 | 262 | ||
@@ -266,18 +269,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
266 | public function testFindPageConfigureInvalid() | 269 | public function testFindPageConfigureInvalid() |
267 | { | 270 | { |
268 | $this->assertNotEquals( | 271 | $this->assertNotEquals( |
269 | Router::$PAGE_CONFIGURE, | 272 | LegacyRouter::$PAGE_CONFIGURE, |
270 | Router::findPage('do=configure', array(), 1) | 273 | LegacyRouter::findPage('do=configure', array(), 1) |
271 | ); | 274 | ); |
272 | 275 | ||
273 | $this->assertNotEquals( | 276 | $this->assertNotEquals( |
274 | Router::$PAGE_CONFIGURE, | 277 | LegacyRouter::$PAGE_CONFIGURE, |
275 | Router::findPage('do=configure', array(), false) | 278 | LegacyRouter::findPage('do=configure', array(), false) |
276 | ); | 279 | ); |
277 | 280 | ||
278 | $this->assertNotEquals( | 281 | $this->assertNotEquals( |
279 | Router::$PAGE_CONFIGURE, | 282 | LegacyRouter::$PAGE_CONFIGURE, |
280 | Router::findPage('do=other', array(), true) | 283 | LegacyRouter::findPage('do=other', array(), true) |
281 | ); | 284 | ); |
282 | } | 285 | } |
283 | 286 | ||
@@ -290,13 +293,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
290 | public function testFindPageChangetagValid() | 293 | public function testFindPageChangetagValid() |
291 | { | 294 | { |
292 | $this->assertEquals( | 295 | $this->assertEquals( |
293 | Router::$PAGE_CHANGETAG, | 296 | LegacyRouter::$PAGE_CHANGETAG, |
294 | Router::findPage('do=changetag', array(), true) | 297 | LegacyRouter::findPage('do=changetag', array(), true) |
295 | ); | 298 | ); |
296 | 299 | ||
297 | $this->assertEquals( | 300 | $this->assertEquals( |
298 | Router::$PAGE_CHANGETAG, | 301 | LegacyRouter::$PAGE_CHANGETAG, |
299 | Router::findPage('do=changetag&stuff', array(), true) | 302 | LegacyRouter::findPage('do=changetag&stuff', array(), true) |
300 | ); | 303 | ); |
301 | } | 304 | } |
302 | 305 | ||
@@ -309,18 +312,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
309 | public function testFindPageChangetagInvalid() | 312 | public function testFindPageChangetagInvalid() |
310 | { | 313 | { |
311 | $this->assertNotEquals( | 314 | $this->assertNotEquals( |
312 | Router::$PAGE_CHANGETAG, | 315 | LegacyRouter::$PAGE_CHANGETAG, |
313 | Router::findPage('do=changetag', array(), 1) | 316 | LegacyRouter::findPage('do=changetag', array(), 1) |
314 | ); | 317 | ); |
315 | 318 | ||
316 | $this->assertNotEquals( | 319 | $this->assertNotEquals( |
317 | Router::$PAGE_CHANGETAG, | 320 | LegacyRouter::$PAGE_CHANGETAG, |
318 | Router::findPage('do=changetag', array(), false) | 321 | LegacyRouter::findPage('do=changetag', array(), false) |
319 | ); | 322 | ); |
320 | 323 | ||
321 | $this->assertNotEquals( | 324 | $this->assertNotEquals( |
322 | Router::$PAGE_CHANGETAG, | 325 | LegacyRouter::$PAGE_CHANGETAG, |
323 | Router::findPage('do=other', array(), true) | 326 | LegacyRouter::findPage('do=other', array(), true) |
324 | ); | 327 | ); |
325 | } | 328 | } |
326 | 329 | ||
@@ -333,13 +336,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
333 | public function testFindPageAddlinkValid() | 336 | public function testFindPageAddlinkValid() |
334 | { | 337 | { |
335 | $this->assertEquals( | 338 | $this->assertEquals( |
336 | Router::$PAGE_ADDLINK, | 339 | LegacyRouter::$PAGE_ADDLINK, |
337 | Router::findPage('do=addlink', array(), true) | 340 | LegacyRouter::findPage('do=addlink', array(), true) |
338 | ); | 341 | ); |
339 | 342 | ||
340 | $this->assertEquals( | 343 | $this->assertEquals( |
341 | Router::$PAGE_ADDLINK, | 344 | LegacyRouter::$PAGE_ADDLINK, |
342 | Router::findPage('do=addlink&stuff', array(), true) | 345 | LegacyRouter::findPage('do=addlink&stuff', array(), true) |
343 | ); | 346 | ); |
344 | } | 347 | } |
345 | 348 | ||
@@ -352,18 +355,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
352 | public function testFindPageAddlinkInvalid() | 355 | public function testFindPageAddlinkInvalid() |
353 | { | 356 | { |
354 | $this->assertNotEquals( | 357 | $this->assertNotEquals( |
355 | Router::$PAGE_ADDLINK, | 358 | LegacyRouter::$PAGE_ADDLINK, |
356 | Router::findPage('do=addlink', array(), 1) | 359 | LegacyRouter::findPage('do=addlink', array(), 1) |
357 | ); | 360 | ); |
358 | 361 | ||
359 | $this->assertNotEquals( | 362 | $this->assertNotEquals( |
360 | Router::$PAGE_ADDLINK, | 363 | LegacyRouter::$PAGE_ADDLINK, |
361 | Router::findPage('do=addlink', array(), false) | 364 | LegacyRouter::findPage('do=addlink', array(), false) |
362 | ); | 365 | ); |
363 | 366 | ||
364 | $this->assertNotEquals( | 367 | $this->assertNotEquals( |
365 | Router::$PAGE_ADDLINK, | 368 | LegacyRouter::$PAGE_ADDLINK, |
366 | Router::findPage('do=other', array(), true) | 369 | LegacyRouter::findPage('do=other', array(), true) |
367 | ); | 370 | ); |
368 | } | 371 | } |
369 | 372 | ||
@@ -376,13 +379,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
376 | public function testFindPageExportValid() | 379 | public function testFindPageExportValid() |
377 | { | 380 | { |
378 | $this->assertEquals( | 381 | $this->assertEquals( |
379 | Router::$PAGE_EXPORT, | 382 | LegacyRouter::$PAGE_EXPORT, |
380 | Router::findPage('do=export', array(), true) | 383 | LegacyRouter::findPage('do=export', array(), true) |
381 | ); | 384 | ); |
382 | 385 | ||
383 | $this->assertEquals( | 386 | $this->assertEquals( |
384 | Router::$PAGE_EXPORT, | 387 | LegacyRouter::$PAGE_EXPORT, |
385 | Router::findPage('do=export&stuff', array(), true) | 388 | LegacyRouter::findPage('do=export&stuff', array(), true) |
386 | ); | 389 | ); |
387 | } | 390 | } |
388 | 391 | ||
@@ -395,18 +398,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
395 | public function testFindPageExportInvalid() | 398 | public function testFindPageExportInvalid() |
396 | { | 399 | { |
397 | $this->assertNotEquals( | 400 | $this->assertNotEquals( |
398 | Router::$PAGE_EXPORT, | 401 | LegacyRouter::$PAGE_EXPORT, |
399 | Router::findPage('do=export', array(), 1) | 402 | LegacyRouter::findPage('do=export', array(), 1) |
400 | ); | 403 | ); |
401 | 404 | ||
402 | $this->assertNotEquals( | 405 | $this->assertNotEquals( |
403 | Router::$PAGE_EXPORT, | 406 | LegacyRouter::$PAGE_EXPORT, |
404 | Router::findPage('do=export', array(), false) | 407 | LegacyRouter::findPage('do=export', array(), false) |
405 | ); | 408 | ); |
406 | 409 | ||
407 | $this->assertNotEquals( | 410 | $this->assertNotEquals( |
408 | Router::$PAGE_EXPORT, | 411 | LegacyRouter::$PAGE_EXPORT, |
409 | Router::findPage('do=other', array(), true) | 412 | LegacyRouter::findPage('do=other', array(), true) |
410 | ); | 413 | ); |
411 | } | 414 | } |
412 | 415 | ||
@@ -419,13 +422,13 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
419 | public function testFindPageImportValid() | 422 | public function testFindPageImportValid() |
420 | { | 423 | { |
421 | $this->assertEquals( | 424 | $this->assertEquals( |
422 | Router::$PAGE_IMPORT, | 425 | LegacyRouter::$PAGE_IMPORT, |
423 | Router::findPage('do=import', array(), true) | 426 | LegacyRouter::findPage('do=import', array(), true) |
424 | ); | 427 | ); |
425 | 428 | ||
426 | $this->assertEquals( | 429 | $this->assertEquals( |
427 | Router::$PAGE_IMPORT, | 430 | LegacyRouter::$PAGE_IMPORT, |
428 | Router::findPage('do=import&stuff', array(), true) | 431 | LegacyRouter::findPage('do=import&stuff', array(), true) |
429 | ); | 432 | ); |
430 | } | 433 | } |
431 | 434 | ||
@@ -438,18 +441,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
438 | public function testFindPageImportInvalid() | 441 | public function testFindPageImportInvalid() |
439 | { | 442 | { |
440 | $this->assertNotEquals( | 443 | $this->assertNotEquals( |
441 | Router::$PAGE_IMPORT, | 444 | LegacyRouter::$PAGE_IMPORT, |
442 | Router::findPage('do=import', array(), 1) | 445 | LegacyRouter::findPage('do=import', array(), 1) |
443 | ); | 446 | ); |
444 | 447 | ||
445 | $this->assertNotEquals( | 448 | $this->assertNotEquals( |
446 | Router::$PAGE_IMPORT, | 449 | LegacyRouter::$PAGE_IMPORT, |
447 | Router::findPage('do=import', array(), false) | 450 | LegacyRouter::findPage('do=import', array(), false) |
448 | ); | 451 | ); |
449 | 452 | ||
450 | $this->assertNotEquals( | 453 | $this->assertNotEquals( |
451 | Router::$PAGE_IMPORT, | 454 | LegacyRouter::$PAGE_IMPORT, |
452 | Router::findPage('do=other', array(), true) | 455 | LegacyRouter::findPage('do=other', array(), true) |
453 | ); | 456 | ); |
454 | } | 457 | } |
455 | 458 | ||
@@ -462,24 +465,24 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
462 | public function testFindPageEditlinkValid() | 465 | public function testFindPageEditlinkValid() |
463 | { | 466 | { |
464 | $this->assertEquals( | 467 | $this->assertEquals( |
465 | Router::$PAGE_EDITLINK, | 468 | LegacyRouter::$PAGE_EDITLINK, |
466 | Router::findPage('whatever', array('edit_link' => 1), true) | 469 | LegacyRouter::findPage('whatever', array('edit_link' => 1), true) |
467 | ); | 470 | ); |
468 | 471 | ||
469 | $this->assertEquals( | 472 | $this->assertEquals( |
470 | Router::$PAGE_EDITLINK, | 473 | LegacyRouter::$PAGE_EDITLINK, |
471 | Router::findPage('', array('edit_link' => 1), true) | 474 | LegacyRouter::findPage('', array('edit_link' => 1), true) |
472 | ); | 475 | ); |
473 | 476 | ||
474 | 477 | ||
475 | $this->assertEquals( | 478 | $this->assertEquals( |
476 | Router::$PAGE_EDITLINK, | 479 | LegacyRouter::$PAGE_EDITLINK, |
477 | Router::findPage('whatever', array('post' => 1), true) | 480 | LegacyRouter::findPage('whatever', array('post' => 1), true) |
478 | ); | 481 | ); |
479 | 482 | ||
480 | $this->assertEquals( | 483 | $this->assertEquals( |
481 | Router::$PAGE_EDITLINK, | 484 | LegacyRouter::$PAGE_EDITLINK, |
482 | Router::findPage('whatever', array('post' => 1, 'edit_link' => 1), true) | 485 | LegacyRouter::findPage('whatever', array('post' => 1, 'edit_link' => 1), true) |
483 | ); | 486 | ); |
484 | } | 487 | } |
485 | 488 | ||
@@ -492,18 +495,18 @@ class RouterTest extends \PHPUnit\Framework\TestCase | |||
492 | public function testFindPageEditlinkInvalid() | 495 | public function testFindPageEditlinkInvalid() |
493 | { | 496 | { |
494 | $this->assertNotEquals( | 497 | $this->assertNotEquals( |
495 | Router::$PAGE_EDITLINK, | 498 | LegacyRouter::$PAGE_EDITLINK, |
496 | Router::findPage('whatever', array('edit_link' => 1), false) | 499 | LegacyRouter::findPage('whatever', array('edit_link' => 1), false) |
497 | ); | 500 | ); |
498 | 501 | ||
499 | $this->assertNotEquals( | 502 | $this->assertNotEquals( |
500 | Router::$PAGE_EDITLINK, | 503 | LegacyRouter::$PAGE_EDITLINK, |
501 | Router::findPage('whatever', array('edit_link' => 1), 1) | 504 | LegacyRouter::findPage('whatever', array('edit_link' => 1), 1) |
502 | ); | 505 | ); |
503 | 506 | ||
504 | $this->assertNotEquals( | 507 | $this->assertNotEquals( |
505 | Router::$PAGE_EDITLINK, | 508 | LegacyRouter::$PAGE_EDITLINK, |
506 | Router::findPage('whatever', array(), true) | 509 | LegacyRouter::findPage('whatever', array(), true) |
507 | ); | 510 | ); |
508 | } | 511 | } |
509 | } | 512 | } |
diff --git a/tests/netscape/BookmarkExportTest.php b/tests/netscape/BookmarkExportTest.php index 6c948bba..509da51d 100644 --- a/tests/netscape/BookmarkExportTest.php +++ b/tests/netscape/BookmarkExportTest.php | |||
@@ -1,11 +1,12 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Netscape; | 3 | namespace Shaarli\Netscape; |
3 | 4 | ||
5 | use PHPUnit\Framework\TestCase; | ||
4 | use Shaarli\Bookmark\BookmarkFileService; | 6 | use Shaarli\Bookmark\BookmarkFileService; |
5 | use Shaarli\Bookmark\LinkDB; | ||
6 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
7 | use Shaarli\Formatter\FormatterFactory; | ||
8 | use Shaarli\Formatter\BookmarkFormatter; | 8 | use Shaarli\Formatter\BookmarkFormatter; |
9 | use Shaarli\Formatter\FormatterFactory; | ||
9 | use Shaarli\History; | 10 | use Shaarli\History; |
10 | 11 | ||
11 | require_once 'tests/utils/ReferenceLinkDB.php'; | 12 | require_once 'tests/utils/ReferenceLinkDB.php'; |
@@ -13,7 +14,7 @@ require_once 'tests/utils/ReferenceLinkDB.php'; | |||
13 | /** | 14 | /** |
14 | * Netscape bookmark export | 15 | * Netscape bookmark export |
15 | */ | 16 | */ |
16 | class BookmarkExportTest extends \PHPUnit\Framework\TestCase | 17 | class BookmarkExportTest extends TestCase |
17 | { | 18 | { |
18 | /** | 19 | /** |
19 | * @var string datastore to test write operations | 20 | * @var string datastore to test write operations |
@@ -21,6 +22,11 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
21 | protected static $testDatastore = 'sandbox/datastore.php'; | 22 | protected static $testDatastore = 'sandbox/datastore.php'; |
22 | 23 | ||
23 | /** | 24 | /** |
25 | * @var ConfigManager instance. | ||
26 | */ | ||
27 | protected static $conf; | ||
28 | |||
29 | /** | ||
24 | * @var \ReferenceLinkDB instance. | 30 | * @var \ReferenceLinkDB instance. |
25 | */ | 31 | */ |
26 | protected static $refDb = null; | 32 | protected static $refDb = null; |
@@ -36,18 +42,37 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
36 | protected static $formatter; | 42 | protected static $formatter; |
37 | 43 | ||
38 | /** | 44 | /** |
45 | * @var History instance | ||
46 | */ | ||
47 | protected static $history; | ||
48 | |||
49 | /** | ||
50 | * @var NetscapeBookmarkUtils | ||
51 | */ | ||
52 | protected $netscapeBookmarkUtils; | ||
53 | |||
54 | /** | ||
39 | * Instantiate reference data | 55 | * Instantiate reference data |
40 | */ | 56 | */ |
41 | public static function setUpBeforeClass() | 57 | public static function setUpBeforeClass() |
42 | { | 58 | { |
43 | $conf = new ConfigManager('tests/utils/config/configJson'); | 59 | static::$conf = new ConfigManager('tests/utils/config/configJson'); |
44 | $conf->set('resource.datastore', self::$testDatastore); | 60 | static::$conf->set('resource.datastore', static::$testDatastore); |
45 | self::$refDb = new \ReferenceLinkDB(); | 61 | static::$refDb = new \ReferenceLinkDB(); |
46 | self::$refDb->write(self::$testDatastore); | 62 | static::$refDb->write(static::$testDatastore); |
47 | $history = new History('sandbox/history.php'); | 63 | static::$history = new History('sandbox/history.php'); |
48 | self::$bookmarkService = new BookmarkFileService($conf, $history, true); | 64 | static::$bookmarkService = new BookmarkFileService(static::$conf, static::$history, true); |
49 | $factory = new FormatterFactory($conf, true); | 65 | $factory = new FormatterFactory(static::$conf, true); |
50 | self::$formatter = $factory->getFormatter('raw'); | 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 | ); | ||
51 | } | 76 | } |
52 | 77 | ||
53 | /** | 78 | /** |
@@ -57,8 +82,7 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
57 | */ | 82 | */ |
58 | public function testFilterAndFormatInvalid() | 83 | public function testFilterAndFormatInvalid() |
59 | { | 84 | { |
60 | NetscapeBookmarkUtils::filterAndFormat( | 85 | $this->netscapeBookmarkUtils->filterAndFormat( |
61 | self::$bookmarkService, | ||
62 | self::$formatter, | 86 | self::$formatter, |
63 | 'derp', | 87 | 'derp', |
64 | false, | 88 | false, |
@@ -71,8 +95,7 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
71 | */ | 95 | */ |
72 | public function testFilterAndFormatAll() | 96 | public function testFilterAndFormatAll() |
73 | { | 97 | { |
74 | $links = NetscapeBookmarkUtils::filterAndFormat( | 98 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
75 | self::$bookmarkService, | ||
76 | self::$formatter, | 99 | self::$formatter, |
77 | 'all', | 100 | 'all', |
78 | false, | 101 | false, |
@@ -97,8 +120,7 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
97 | */ | 120 | */ |
98 | public function testFilterAndFormatPrivate() | 121 | public function testFilterAndFormatPrivate() |
99 | { | 122 | { |
100 | $links = NetscapeBookmarkUtils::filterAndFormat( | 123 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
101 | self::$bookmarkService, | ||
102 | self::$formatter, | 124 | self::$formatter, |
103 | 'private', | 125 | 'private', |
104 | false, | 126 | false, |
@@ -123,8 +145,7 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
123 | */ | 145 | */ |
124 | public function testFilterAndFormatPublic() | 146 | public function testFilterAndFormatPublic() |
125 | { | 147 | { |
126 | $links = NetscapeBookmarkUtils::filterAndFormat( | 148 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
127 | self::$bookmarkService, | ||
128 | self::$formatter, | 149 | self::$formatter, |
129 | 'public', | 150 | 'public', |
130 | false, | 151 | false, |
@@ -149,15 +170,14 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
149 | */ | 170 | */ |
150 | public function testFilterAndFormatDoNotPrependNoteUrl() | 171 | public function testFilterAndFormatDoNotPrependNoteUrl() |
151 | { | 172 | { |
152 | $links = NetscapeBookmarkUtils::filterAndFormat( | 173 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
153 | self::$bookmarkService, | ||
154 | self::$formatter, | 174 | self::$formatter, |
155 | 'public', | 175 | 'public', |
156 | false, | 176 | false, |
157 | '' | 177 | '' |
158 | ); | 178 | ); |
159 | $this->assertEquals( | 179 | $this->assertEquals( |
160 | '?WDWyig', | 180 | '/shaare/WDWyig', |
161 | $links[2]['url'] | 181 | $links[2]['url'] |
162 | ); | 182 | ); |
163 | } | 183 | } |
@@ -168,15 +188,14 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
168 | public function testFilterAndFormatPrependNoteUrl() | 188 | public function testFilterAndFormatPrependNoteUrl() |
169 | { | 189 | { |
170 | $indexUrl = 'http://localhost:7469/shaarli/'; | 190 | $indexUrl = 'http://localhost:7469/shaarli/'; |
171 | $links = NetscapeBookmarkUtils::filterAndFormat( | 191 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
172 | self::$bookmarkService, | ||
173 | self::$formatter, | 192 | self::$formatter, |
174 | 'public', | 193 | 'public', |
175 | true, | 194 | true, |
176 | $indexUrl | 195 | $indexUrl |
177 | ); | 196 | ); |
178 | $this->assertEquals( | 197 | $this->assertEquals( |
179 | $indexUrl . '?WDWyig', | 198 | $indexUrl . 'shaare/WDWyig', |
180 | $links[2]['url'] | 199 | $links[2]['url'] |
181 | ); | 200 | ); |
182 | } | 201 | } |
diff --git a/tests/netscape/BookmarkImportTest.php b/tests/netscape/BookmarkImportTest.php index fef7f6d1..f678e26b 100644 --- a/tests/netscape/BookmarkImportTest.php +++ b/tests/netscape/BookmarkImportTest.php | |||
@@ -1,29 +1,31 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Netscape; | 3 | namespace Shaarli\Netscape; |
3 | 4 | ||
4 | use DateTime; | 5 | use DateTime; |
6 | use PHPUnit\Framework\TestCase; | ||
7 | use Psr\Http\Message\UploadedFileInterface; | ||
5 | use Shaarli\Bookmark\Bookmark; | 8 | use Shaarli\Bookmark\Bookmark; |
6 | use Shaarli\Bookmark\BookmarkFilter; | ||
7 | use Shaarli\Bookmark\BookmarkFileService; | 9 | use Shaarli\Bookmark\BookmarkFileService; |
8 | use Shaarli\Bookmark\LinkDB; | 10 | use Shaarli\Bookmark\BookmarkFilter; |
9 | use Shaarli\Config\ConfigManager; | 11 | use Shaarli\Config\ConfigManager; |
10 | use Shaarli\History; | 12 | use Shaarli\History; |
13 | use Slim\Http\UploadedFile; | ||
11 | 14 | ||
12 | /** | 15 | /** |
13 | * 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 |
14 | * | 17 | * |
15 | * @param string $filename Basename of the file | 18 | * @param string $filename Basename of the file |
16 | * | 19 | * |
17 | * @return array A $_FILES-like array | 20 | * @return UploadedFileInterface Upload file in PSR-7 compatible object |
18 | */ | 21 | */ |
19 | function file2array($filename) | 22 | function file2array($filename) |
20 | { | 23 | { |
21 | return array( | 24 | return new UploadedFile( |
22 | 'filetoupload' => array( | 25 | __DIR__ . '/input/' . $filename, |
23 | 'name' => $filename, | 26 | $filename, |
24 | 'tmp_name' => __DIR__ . '/input/' . $filename, | 27 | null, |
25 | 'size' => filesize(__DIR__ . '/input/' . $filename) | 28 | filesize(__DIR__ . '/input/' . $filename) |
26 | ) | ||
27 | ); | 29 | ); |
28 | } | 30 | } |
29 | 31 | ||
@@ -31,7 +33,7 @@ function file2array($filename) | |||
31 | /** | 33 | /** |
32 | * Netscape bookmark import | 34 | * Netscape bookmark import |
33 | */ | 35 | */ |
34 | class BookmarkImportTest extends \PHPUnit\Framework\TestCase | 36 | class BookmarkImportTest extends TestCase |
35 | { | 37 | { |
36 | /** | 38 | /** |
37 | * @var string datastore to test write operations | 39 | * @var string datastore to test write operations |
@@ -64,6 +66,11 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
64 | protected $history; | 66 | protected $history; |
65 | 67 | ||
66 | /** | 68 | /** |
69 | * @var NetscapeBookmarkUtils | ||
70 | */ | ||
71 | protected $netscapeBookmarkUtils; | ||
72 | |||
73 | /** | ||
67 | * @var string Save the current timezone. | 74 | * @var string Save the current timezone. |
68 | */ | 75 | */ |
69 | protected static $defaultTimeZone; | 76 | protected static $defaultTimeZone; |
@@ -91,6 +98,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
91 | $this->conf->set('resource.datastore', self::$testDatastore); | 98 | $this->conf->set('resource.datastore', self::$testDatastore); |
92 | $this->history = new History(self::$historyFilePath); | 99 | $this->history = new History(self::$historyFilePath); |
93 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | 100 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); |
101 | $this->netscapeBookmarkUtils = new NetscapeBookmarkUtils($this->bookmarkService, $this->conf, $this->history); | ||
94 | } | 102 | } |
95 | 103 | ||
96 | /** | 104 | /** |
@@ -115,7 +123,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
115 | $this->assertEquals( | 123 | $this->assertEquals( |
116 | 'File empty.htm (0 bytes) has an unknown file format.' | 124 | 'File empty.htm (0 bytes) has an unknown file format.' |
117 | .' Nothing was imported.', | 125 | .' Nothing was imported.', |
118 | NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history) | 126 | $this->netscapeBookmarkUtils->import(null, $files) |
119 | ); | 127 | ); |
120 | $this->assertEquals(0, $this->bookmarkService->count()); | 128 | $this->assertEquals(0, $this->bookmarkService->count()); |
121 | } | 129 | } |
@@ -128,7 +136,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
128 | $files = file2array('no_doctype.htm'); | 136 | $files = file2array('no_doctype.htm'); |
129 | $this->assertEquals( | 137 | $this->assertEquals( |
130 | '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.', |
131 | NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history) | 139 | $this->netscapeBookmarkUtils->import(null, $files) |
132 | ); | 140 | ); |
133 | $this->assertEquals(0, $this->bookmarkService->count()); | 141 | $this->assertEquals(0, $this->bookmarkService->count()); |
134 | } | 142 | } |
@@ -142,7 +150,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
142 | $this->assertStringMatchesFormat( | 150 | $this->assertStringMatchesFormat( |
143 | '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:' |
144 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 152 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
145 | NetscapeBookmarkUtils::import(null, $files, $this->bookmarkService, $this->conf, $this->history) | 153 | $this->netscapeBookmarkUtils->import(null, $files) |
146 | ); | 154 | ); |
147 | $this->assertEquals(2, $this->bookmarkService->count()); | 155 | $this->assertEquals(2, $this->bookmarkService->count()); |
148 | } | 156 | } |
@@ -157,7 +165,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
157 | $this->assertStringMatchesFormat( | 165 | $this->assertStringMatchesFormat( |
158 | '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:' |
159 | .' 1 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 167 | .' 1 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
160 | NetscapeBookmarkUtils::import([], $files, $this->bookmarkService, $this->conf, $this->history) | 168 | $this->netscapeBookmarkUtils->import([], $files) |
161 | ); | 169 | ); |
162 | $this->assertEquals(1, $this->bookmarkService->count()); | 170 | $this->assertEquals(1, $this->bookmarkService->count()); |
163 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 171 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -185,7 +193,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
185 | $this->assertStringMatchesFormat( | 193 | $this->assertStringMatchesFormat( |
186 | '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:' |
187 | .' 8 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 195 | .' 8 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
188 | NetscapeBookmarkUtils::import([], $files, $this->bookmarkService, $this->conf, $this->history) | 196 | $this->netscapeBookmarkUtils->import([], $files) |
189 | ); | 197 | ); |
190 | $this->assertEquals(8, $this->bookmarkService->count()); | 198 | $this->assertEquals(8, $this->bookmarkService->count()); |
191 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 199 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -306,7 +314,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
306 | $this->assertStringMatchesFormat( | 314 | $this->assertStringMatchesFormat( |
307 | '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:' |
308 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 316 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
309 | NetscapeBookmarkUtils::import([], $files, $this->bookmarkService, $this->conf, $this->history) | 317 | $this->netscapeBookmarkUtils->import([], $files) |
310 | ); | 318 | ); |
311 | 319 | ||
312 | $this->assertEquals(2, $this->bookmarkService->count()); | 320 | $this->assertEquals(2, $this->bookmarkService->count()); |
@@ -349,7 +357,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
349 | $this->assertStringMatchesFormat( | 357 | $this->assertStringMatchesFormat( |
350 | '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:' |
351 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 359 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
352 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 360 | $this->netscapeBookmarkUtils->import($post, $files) |
353 | ); | 361 | ); |
354 | 362 | ||
355 | $this->assertEquals(2, $this->bookmarkService->count()); | 363 | $this->assertEquals(2, $this->bookmarkService->count()); |
@@ -392,7 +400,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
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 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 402 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
395 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 403 | $this->netscapeBookmarkUtils->import($post, $files) |
396 | ); | 404 | ); |
397 | $this->assertEquals(2, $this->bookmarkService->count()); | 405 | $this->assertEquals(2, $this->bookmarkService->count()); |
398 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 406 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -410,7 +418,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
410 | $this->assertStringMatchesFormat( | 418 | $this->assertStringMatchesFormat( |
411 | '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:' |
412 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 420 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
413 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 421 | $this->netscapeBookmarkUtils->import($post, $files) |
414 | ); | 422 | ); |
415 | $this->assertEquals(2, $this->bookmarkService->count()); | 423 | $this->assertEquals(2, $this->bookmarkService->count()); |
416 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 424 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -430,7 +438,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
430 | $this->assertStringMatchesFormat( | 438 | $this->assertStringMatchesFormat( |
431 | '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:' |
432 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 440 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
433 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 441 | $this->netscapeBookmarkUtils->import($post, $files) |
434 | ); | 442 | ); |
435 | $this->assertEquals(2, $this->bookmarkService->count()); | 443 | $this->assertEquals(2, $this->bookmarkService->count()); |
436 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 444 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -445,7 +453,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
445 | $this->assertStringMatchesFormat( | 453 | $this->assertStringMatchesFormat( |
446 | '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:' |
447 | .' 2 bookmarks imported, 2 bookmarks overwritten, 0 bookmarks skipped.', | 455 | .' 2 bookmarks imported, 2 bookmarks overwritten, 0 bookmarks skipped.', |
448 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 456 | $this->netscapeBookmarkUtils->import($post, $files) |
449 | ); | 457 | ); |
450 | $this->assertEquals(2, $this->bookmarkService->count()); | 458 | $this->assertEquals(2, $this->bookmarkService->count()); |
451 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 459 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -465,7 +473,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
465 | $this->assertStringMatchesFormat( | 473 | $this->assertStringMatchesFormat( |
466 | '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:' |
467 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 475 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
468 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 476 | $this->netscapeBookmarkUtils->import($post, $files) |
469 | ); | 477 | ); |
470 | $this->assertEquals(2, $this->bookmarkService->count()); | 478 | $this->assertEquals(2, $this->bookmarkService->count()); |
471 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 479 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -480,7 +488,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
480 | $this->assertStringMatchesFormat( | 488 | $this->assertStringMatchesFormat( |
481 | '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:' |
482 | .' 2 bookmarks imported, 2 bookmarks overwritten, 0 bookmarks skipped.', | 490 | .' 2 bookmarks imported, 2 bookmarks overwritten, 0 bookmarks skipped.', |
483 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 491 | $this->netscapeBookmarkUtils->import($post, $files) |
484 | ); | 492 | ); |
485 | $this->assertEquals(2, $this->bookmarkService->count()); | 493 | $this->assertEquals(2, $this->bookmarkService->count()); |
486 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 494 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -498,7 +506,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
498 | $this->assertStringMatchesFormat( | 506 | $this->assertStringMatchesFormat( |
499 | '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:' |
500 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 508 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
501 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 509 | $this->netscapeBookmarkUtils->import($post, $files) |
502 | ); | 510 | ); |
503 | $this->assertEquals(2, $this->bookmarkService->count()); | 511 | $this->assertEquals(2, $this->bookmarkService->count()); |
504 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 512 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -508,7 +516,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
508 | $this->assertStringMatchesFormat( | 516 | $this->assertStringMatchesFormat( |
509 | '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:' |
510 | .' 0 bookmarks imported, 0 bookmarks overwritten, 2 bookmarks skipped.', | 518 | .' 0 bookmarks imported, 0 bookmarks overwritten, 2 bookmarks skipped.', |
511 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 519 | $this->netscapeBookmarkUtils->import($post, $files) |
512 | ); | 520 | ); |
513 | $this->assertEquals(2, $this->bookmarkService->count()); | 521 | $this->assertEquals(2, $this->bookmarkService->count()); |
514 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 522 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -527,7 +535,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
527 | $this->assertStringMatchesFormat( | 535 | $this->assertStringMatchesFormat( |
528 | '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:' |
529 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 537 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
530 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 538 | $this->netscapeBookmarkUtils->import($post, $files) |
531 | ); | 539 | ); |
532 | $this->assertEquals(2, $this->bookmarkService->count()); | 540 | $this->assertEquals(2, $this->bookmarkService->count()); |
533 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 541 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -548,7 +556,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
548 | $this->assertStringMatchesFormat( | 556 | $this->assertStringMatchesFormat( |
549 | '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:' |
550 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 558 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
551 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history) | 559 | $this->netscapeBookmarkUtils->import($post, $files) |
552 | ); | 560 | ); |
553 | $this->assertEquals(2, $this->bookmarkService->count()); | 561 | $this->assertEquals(2, $this->bookmarkService->count()); |
554 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 562 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -573,7 +581,7 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
573 | $this->assertStringMatchesFormat( | 581 | $this->assertStringMatchesFormat( |
574 | '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:' |
575 | .' 3 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', | 583 | .' 3 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
576 | NetscapeBookmarkUtils::import(array(), $files, $this->bookmarkService, $this->conf, $this->history) | 584 | $this->netscapeBookmarkUtils->import(array(), $files) |
577 | ); | 585 | ); |
578 | $this->assertEquals(3, $this->bookmarkService->count()); | 586 | $this->assertEquals(3, $this->bookmarkService->count()); |
579 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | 587 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
@@ -589,14 +597,14 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
589 | 'overwrite' => 'true', | 597 | 'overwrite' => 'true', |
590 | ]; | 598 | ]; |
591 | $files = file2array('netscape_basic.htm'); | 599 | $files = file2array('netscape_basic.htm'); |
592 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history); | 600 | $this->netscapeBookmarkUtils->import($post, $files); |
593 | $history = $this->history->getHistory(); | 601 | $history = $this->history->getHistory(); |
594 | $this->assertEquals(1, count($history)); | 602 | $this->assertEquals(1, count($history)); |
595 | $this->assertEquals(History::IMPORT, $history[0]['event']); | 603 | $this->assertEquals(History::IMPORT, $history[0]['event']); |
596 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); | 604 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); |
597 | 605 | ||
598 | // re-import as private, enable overwriting | 606 | // re-import as private, enable overwriting |
599 | NetscapeBookmarkUtils::import($post, $files, $this->bookmarkService, $this->conf, $this->history); | 607 | $this->netscapeBookmarkUtils->import($post, $files); |
600 | $history = $this->history->getHistory(); | 608 | $history = $this->history->getHistory(); |
601 | $this->assertEquals(2, count($history)); | 609 | $this->assertEquals(2, count($history)); |
602 | $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..aa5c6988 100644 --- a/tests/plugins/PluginAddlinkTest.php +++ b/tests/plugins/PluginAddlinkTest.php | |||
@@ -2,7 +2,7 @@ | |||
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 | ||
@@ -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/PluginPlayvideosTest.php b/tests/plugins/PluginPlayvideosTest.php index 51472617..b7b6ce53 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 | ||
@@ -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..e66f484e 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 | ||
@@ -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..c9f8c733 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 | ||
@@ -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/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 | ![link](/img/train.png) | ||
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..ae5032dd 100644 --- a/tests/plugins/test/test.php +++ b/tests/plugins/test/test.php | |||
@@ -19,3 +19,8 @@ function hook_test_random($data) | |||
19 | 19 | ||
20 | return $data; | 20 | return $data; |
21 | } | 21 | } |
22 | |||
23 | function hook_test_error() | ||
24 | { | ||
25 | new Unknown(); | ||
26 | } | ||
diff --git a/tests/feed/CacheTest.php b/tests/render/PageCacheManagerTest.php index c0a9f26f..c258f45f 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 PHPUnit\Framework\TestCase; |
10 | use Shaarli\Security\SessionManager; | ||
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 | public function setUp() |
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 { |
@@ -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/security/LoginManagerTest.php b/tests/security/LoginManagerTest.php index 8fd1698c..f242be09 100644 --- a/tests/security/LoginManagerTest.php +++ b/tests/security/LoginManagerTest.php | |||
@@ -1,7 +1,6 @@ | |||
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 PHPUnit\Framework\TestCase; |
7 | 6 | ||
@@ -58,6 +57,9 @@ 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 | */ |
@@ -84,8 +86,12 @@ class LoginManagerTest extends TestCase | |||
84 | $this->cookie = []; | 86 | $this->cookie = []; |
85 | $this->session = []; | 87 | $this->session = []; |
86 | 88 | ||
87 | $this->sessionManager = new SessionManager($this->session, $this->configManager); | 89 | $this->cookieManager = $this->createMock(CookieManager::class); |
88 | $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); | ||
89 | $this->server['REMOTE_ADDR'] = $this->ipAddr; | 95 | $this->server['REMOTE_ADDR'] = $this->ipAddr; |
90 | } | 96 | } |
91 | 97 | ||
@@ -193,8 +199,8 @@ class LoginManagerTest extends TestCase | |||
193 | $configManager = new \FakeConfigManager([ | 199 | $configManager = new \FakeConfigManager([ |
194 | 'resource.ban_file' => $this->banFile, | 200 | 'resource.ban_file' => $this->banFile, |
195 | ]); | 201 | ]); |
196 | $loginManager = new LoginManager($configManager, null); | 202 | $loginManager = new LoginManager($configManager, null, $this->cookieManager); |
197 | $loginManager->checkLoginState([], ''); | 203 | $loginManager->checkLoginState(''); |
198 | 204 | ||
199 | $this->assertFalse($loginManager->isLoggedIn()); | 205 | $this->assertFalse($loginManager->isLoggedIn()); |
200 | } | 206 | } |
@@ -210,9 +216,9 @@ class LoginManagerTest extends TestCase | |||
210 | 'expires_on' => time() + 100, | 216 | 'expires_on' => time() + 100, |
211 | ]; | 217 | ]; |
212 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 218 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
213 | $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = 'nope'; | 219 | $this->cookie[CookieManager::STAY_SIGNED_IN] = 'nope'; |
214 | 220 | ||
215 | $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress); | 221 | $this->loginManager->checkLoginState($this->clientIpAddress); |
216 | 222 | ||
217 | $this->assertTrue($this->loginManager->isLoggedIn()); | 223 | $this->assertTrue($this->loginManager->isLoggedIn()); |
218 | $this->assertTrue(empty($this->session['username'])); | 224 | $this->assertTrue(empty($this->session['username'])); |
@@ -224,9 +230,9 @@ class LoginManagerTest extends TestCase | |||
224 | public function testCheckLoginStateStaySignedInWithValidToken() | 230 | public function testCheckLoginStateStaySignedInWithValidToken() |
225 | { | 231 | { |
226 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 232 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
227 | $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = $this->loginManager->getStaySignedInToken(); | 233 | $this->cookie[CookieManager::STAY_SIGNED_IN] = $this->loginManager->getStaySignedInToken(); |
228 | 234 | ||
229 | $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress); | 235 | $this->loginManager->checkLoginState($this->clientIpAddress); |
230 | 236 | ||
231 | $this->assertTrue($this->loginManager->isLoggedIn()); | 237 | $this->assertTrue($this->loginManager->isLoggedIn()); |
232 | $this->assertEquals($this->login, $this->session['username']); | 238 | $this->assertEquals($this->login, $this->session['username']); |
@@ -241,7 +247,7 @@ class LoginManagerTest extends TestCase | |||
241 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 247 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
242 | $this->session['expires_on'] = time() - 100; | 248 | $this->session['expires_on'] = time() - 100; |
243 | 249 | ||
244 | $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress); | 250 | $this->loginManager->checkLoginState($this->clientIpAddress); |
245 | 251 | ||
246 | $this->assertFalse($this->loginManager->isLoggedIn()); | 252 | $this->assertFalse($this->loginManager->isLoggedIn()); |
247 | } | 253 | } |
@@ -253,7 +259,7 @@ class LoginManagerTest extends TestCase | |||
253 | { | 259 | { |
254 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 260 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
255 | 261 | ||
256 | $this->loginManager->checkLoginState($this->cookie, '10.7.157.98'); | 262 | $this->loginManager->checkLoginState('10.7.157.98'); |
257 | 263 | ||
258 | $this->assertFalse($this->loginManager->isLoggedIn()); | 264 | $this->assertFalse($this->loginManager->isLoggedIn()); |
259 | } | 265 | } |
diff --git a/tests/security/SessionManagerTest.php b/tests/security/SessionManagerTest.php index f264505e..60695dcf 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 PHPUnit\Framework\TestCase; |
9 | use Shaarli\Security\SessionManager; | ||
10 | 6 | ||
11 | /** | 7 | /** |
12 | * Test coverage for SessionManager | 8 | * Test coverage for SessionManager |
@@ -30,7 +26,7 @@ class SessionManagerTest extends TestCase | |||
30 | */ | 26 | */ |
31 | public static function setUpBeforeClass() | 27 | public static function setUpBeforeClass() |
32 | { | 28 | { |
33 | self::$sidHashes = ReferenceSessionIdHashes::getHashes(); | 29 | self::$sidHashes = \ReferenceSessionIdHashes::getHashes(); |
34 | } | 30 | } |
35 | 31 | ||
36 | /** | 32 | /** |
@@ -38,13 +34,13 @@ class SessionManagerTest extends TestCase | |||
38 | */ | 34 | */ |
39 | public function setUp() | 35 | public function setUp() |
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)); |
@@ -269,4 +265,61 @@ class SessionManagerTest extends TestCase | |||
269 | $this->session['ip'] = 'ip_id_one'; | 265 | $this->session['ip'] = 'ip_id_one'; |
270 | $this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two')); | 266 | $this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two')); |
271 | } | 267 | } |
268 | |||
269 | /** | ||
270 | * Test creating an entry in the session array | ||
271 | */ | ||
272 | public function testSetSessionParameterCreate(): void | ||
273 | { | ||
274 | $this->sessionManager->setSessionParameter('abc', 'def'); | ||
275 | |||
276 | static::assertSame('def', $this->session['abc']); | ||
277 | } | ||
278 | |||
279 | /** | ||
280 | * Test updating an entry in the session array | ||
281 | */ | ||
282 | public function testSetSessionParameterUpdate(): void | ||
283 | { | ||
284 | $this->session['abc'] = 'ghi'; | ||
285 | |||
286 | $this->sessionManager->setSessionParameter('abc', 'def'); | ||
287 | |||
288 | static::assertSame('def', $this->session['abc']); | ||
289 | } | ||
290 | |||
291 | /** | ||
292 | * Test updating an entry in the session array with null value | ||
293 | */ | ||
294 | public function testSetSessionParameterUpdateNull(): void | ||
295 | { | ||
296 | $this->session['abc'] = 'ghi'; | ||
297 | |||
298 | $this->sessionManager->setSessionParameter('abc', null); | ||
299 | |||
300 | static::assertArrayHasKey('abc', $this->session); | ||
301 | static::assertNull($this->session['abc']); | ||
302 | } | ||
303 | |||
304 | /** | ||
305 | * Test deleting an existing entry in the session array | ||
306 | */ | ||
307 | public function testDeleteSessionParameter(): void | ||
308 | { | ||
309 | $this->session['abc'] = 'def'; | ||
310 | |||
311 | $this->sessionManager->deleteSessionParameter('abc'); | ||
312 | |||
313 | static::assertArrayNotHasKey('abc', $this->session); | ||
314 | } | ||
315 | |||
316 | /** | ||
317 | * Test deleting a non existent entry in the session array | ||
318 | */ | ||
319 | public function testDeleteSessionParameterNotExisting(): void | ||
320 | { | ||
321 | $this->sessionManager->deleteSessionParameter('abc'); | ||
322 | |||
323 | static::assertArrayNotHasKey('abc', $this->session); | ||
324 | } | ||
272 | } | 325 | } |
diff --git a/tests/updater/UpdaterTest.php b/tests/updater/UpdaterTest.php index c689982b..a7dd70bf 100644 --- a/tests/updater/UpdaterTest.php +++ b/tests/updater/UpdaterTest.php | |||
@@ -2,17 +2,18 @@ | |||
2 | namespace Shaarli\Updater; | 2 | namespace Shaarli\Updater; |
3 | 3 | ||
4 | use Exception; | 4 | use Exception; |
5 | use PHPUnit\Framework\TestCase; | ||
6 | use Shaarli\Bookmark\BookmarkFileService; | ||
7 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
5 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\History; | ||
6 | 10 | ||
7 | require_once 'tests/updater/DummyUpdater.php'; | ||
8 | require_once 'tests/utils/ReferenceLinkDB.php'; | ||
9 | require_once 'inc/rain.tpl.class.php'; | ||
10 | 11 | ||
11 | /** | 12 | /** |
12 | * Class UpdaterTest. | 13 | * Class UpdaterTest. |
13 | * Runs unit tests against the updater class. | 14 | * Runs unit tests against the updater class. |
14 | */ | 15 | */ |
15 | class UpdaterTest extends \PHPUnit\Framework\TestCase | 16 | class UpdaterTest extends TestCase |
16 | { | 17 | { |
17 | /** | 18 | /** |
18 | * @var string Path to test datastore. | 19 | * @var string Path to test datastore. |
@@ -29,13 +30,27 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase | |||
29 | */ | 30 | */ |
30 | protected $conf; | 31 | protected $conf; |
31 | 32 | ||
33 | /** @var BookmarkServiceInterface */ | ||
34 | protected $bookmarkService; | ||
35 | |||
36 | /** @var \ReferenceLinkDB */ | ||
37 | protected $refDB; | ||
38 | |||
39 | /** @var Updater */ | ||
40 | protected $updater; | ||
41 | |||
32 | /** | 42 | /** |
33 | * Executed before each test. | 43 | * Executed before each test. |
34 | */ | 44 | */ |
35 | public function setUp() | 45 | public function setUp() |
36 | { | 46 | { |
47 | $this->refDB = new \ReferenceLinkDB(); | ||
48 | $this->refDB->write(self::$testDatastore); | ||
49 | |||
37 | copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php'); | 50 | copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php'); |
38 | $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); | ||
39 | } | 54 | } |
40 | 55 | ||
41 | /** | 56 | /** |
@@ -167,4 +182,40 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase | |||
167 | $updater = new DummyUpdater($updates, array(), $this->conf, true); | 182 | $updater = new DummyUpdater($updates, array(), $this->conf, true); |
168 | $updater->update(); | 183 | $updater->update(); |
169 | } | 184 | } |
185 | |||
186 | public function testUpdateMethodRelativeHomeLinkRename(): void | ||
187 | { | ||
188 | $this->updater->setBasePath('/subfolder'); | ||
189 | $this->conf->set('general.header_link', '?'); | ||
190 | |||
191 | $this->updater->updateMethodRelativeHomeLink(); | ||
192 | |||
193 | static::assertSame('/subfolder/', $this->conf->get('general.header_link')); | ||
194 | } | ||
195 | |||
196 | public function testUpdateMethodRelativeHomeLinkDoNotRename(): void | ||
197 | { | ||
198 | $this->conf->set('general.header_link', '~/my-blog'); | ||
199 | |||
200 | $this->updater->updateMethodRelativeHomeLink(); | ||
201 | |||
202 | static::assertSame('~/my-blog', $this->conf->get('general.header_link')); | ||
203 | } | ||
204 | |||
205 | public function testUpdateMethodMigrateExistingNotesUrl(): void | ||
206 | { | ||
207 | $this->updater->updateMethodMigrateExistingNotesUrl(); | ||
208 | |||
209 | static::assertSame($this->refDB->getLinks()[0]->getUrl(), $this->bookmarkService->get(0)->getUrl()); | ||
210 | static::assertSame($this->refDB->getLinks()[1]->getUrl(), $this->bookmarkService->get(1)->getUrl()); | ||
211 | static::assertSame($this->refDB->getLinks()[4]->getUrl(), $this->bookmarkService->get(4)->getUrl()); | ||
212 | static::assertSame($this->refDB->getLinks()[6]->getUrl(), $this->bookmarkService->get(6)->getUrl()); | ||
213 | static::assertSame($this->refDB->getLinks()[7]->getUrl(), $this->bookmarkService->get(7)->getUrl()); | ||
214 | static::assertSame($this->refDB->getLinks()[8]->getUrl(), $this->bookmarkService->get(8)->getUrl()); | ||
215 | static::assertSame($this->refDB->getLinks()[9]->getUrl(), $this->bookmarkService->get(9)->getUrl()); | ||
216 | static::assertSame('/shaare/WDWyig', $this->bookmarkService->get(42)->getUrl()); | ||
217 | static::assertSame('/shaare/WDWyig', $this->bookmarkService->get(41)->getUrl()); | ||
218 | static::assertSame('/shaare/0gCTjQ', $this->bookmarkService->get(10)->getUrl()); | ||
219 | static::assertSame('/shaare/PCRizQ', $this->bookmarkService->get(11)->getUrl()); | ||
220 | } | ||
170 | } | 221 | } |
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php index 0095f5a1..fc3cb109 100644 --- a/tests/utils/ReferenceLinkDB.php +++ b/tests/utils/ReferenceLinkDB.php | |||
@@ -30,7 +30,7 @@ class ReferenceLinkDB | |||
30 | $this->addLink( | 30 | $this->addLink( |
31 | 11, | 31 | 11, |
32 | 'Pined older', | 32 | 'Pined older', |
33 | '?PCRizQ', | 33 | '/shaare/PCRizQ', |
34 | 'This is an older pinned link', | 34 | 'This is an older pinned link', |
35 | 0, | 35 | 0, |
36 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100309_101010'), | 36 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100309_101010'), |
@@ -43,7 +43,7 @@ class ReferenceLinkDB | |||
43 | $this->addLink( | 43 | $this->addLink( |
44 | 10, | 44 | 10, |
45 | 'Pined', | 45 | 'Pined', |
46 | '?0gCTjQ', | 46 | '/shaare/0gCTjQ', |
47 | 'This is a pinned link', | 47 | 'This is a pinned link', |
48 | 0, | 48 | 0, |
49 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121207_152312'), | 49 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121207_152312'), |
@@ -56,7 +56,7 @@ class ReferenceLinkDB | |||
56 | $this->addLink( | 56 | $this->addLink( |
57 | 41, | 57 | 41, |
58 | 'Link title: @website', | 58 | 'Link title: @website', |
59 | '?WDWyig', | 59 | '/shaare/WDWyig', |
60 | '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', |
61 | 0, | 61 | 0, |
62 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), | 62 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), |
@@ -68,7 +68,7 @@ class ReferenceLinkDB | |||
68 | $this->addLink( | 68 | $this->addLink( |
69 | 42, | 69 | 42, |
70 | 'Note: I have a big ID but an old date', | 70 | 'Note: I have a big ID but an old date', |
71 | '?WDWyig', | 71 | '/shaare/WDWyig', |
72 | 'Used to test bookmarks reordering.', | 72 | 'Used to test bookmarks reordering.', |
73 | 0, | 73 | 0, |
74 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100310_101010'), | 74 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100310_101010'), |
diff --git a/tpl/default/404.html b/tpl/default/404.html index 09737b4b..7b696e4c 100644 --- a/tpl/default/404.html +++ b/tpl/default/404.html | |||
@@ -8,7 +8,7 @@ | |||
8 | {include="page.header"} | 8 | {include="page.header"} |
9 | <div id="pageError" class="page-error-container center"> | 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 8b75900d..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> |
@@ -289,7 +289,7 @@ | |||
289 | {if="! $gd_enabled"} | 289 | {if="! $gd_enabled"} |
290 | {'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} |
291 | {elseif="$thumbnails_enabled"} | 291 | {elseif="$thumbnails_enabled"} |
292 | <a href="?do=thumbs_update">{'Synchronize thumbnails'|t}</a> | 292 | <a href="{$base_path}/admin/thumbnails">{'Synchronize thumbnails'|t}</a> |
293 | {/if} | 293 | {/if} |
294 | </span> | 294 | </span> |
295 | </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 d16059a3..568545bd 100644 --- a/tpl/default/editlink.html +++ b/tpl/default/editlink.html | |||
@@ -7,7 +7,11 @@ | |||
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> |
@@ -69,7 +73,7 @@ | |||
69 | <input type="submit" name="save_edit" class="" id="button-save-edit" | 73 | <input type="submit" name="save_edit" class="" id="button-save-edit" |
70 | 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}"> |
71 | {if="!$link_is_new"} | 75 | {if="!$link_is_new"} |
72 | <a href="?delete_link&lf_linkdate={$link.id}&token={$token}" | 76 | <a href="{$base_path}/admin/shaare/delete?id={$link.id}&token={$token}" |
73 | title="" name="delete_link" class="button button-red confirm-delete"> | 77 | title="" name="delete_link" class="button button-red confirm-delete"> |
74 | {'Delete'|t} | 78 | {'Delete'|t} |
75 | </a> | 79 | </a> |
@@ -77,6 +81,7 @@ | |||
77 | </div> | 81 | </div> |
78 | 82 | ||
79 | <input type="hidden" name="token" value="{$token}"> | 83 | <input type="hidden" name="token" value="{$token}"> |
84 | <input type="hidden" name="source" value="{$source}"> | ||
80 | {if="$http_referer"} | 85 | {if="$http_referer"} |
81 | <input type="hidden" name="returnurl" value="{$http_referer}"> | 86 | <input type="hidden" name="returnurl" value="{$http_referer}"> |
82 | {/if} | 87 | {/if} |
diff --git a/tpl/default/error.html b/tpl/default/error.html index ef1dfd73..c3e0c3c1 100644 --- a/tpl/default/error.html +++ b/tpl/default/error.html | |||
@@ -15,7 +15,7 @@ | |||
15 | </pre> | 15 | </pre> |
16 | {/if} | 16 | {/if} |
17 | 17 | ||
18 | <img src="img/sad_star.png" alt=""> | 18 | <img src="{$asset_path}/img/sad_star.png#" alt=""> |
19 | </div> | 19 | </div> |
20 | {include="page.footer"} | 20 | {include="page.footer"} |
21 | </body> | 21 | </body> |
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 bcfa7012..dd58bd1e 100644 --- a/tpl/default/feed.atom.html +++ b/tpl/default/feed.atom.html | |||
@@ -6,6 +6,8 @@ | |||
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} |
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 3820a4f7..227f9b52 100644 --- a/tpl/default/includes.html +++ b/tpl/default/includes.html | |||
@@ -3,21 +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'"} | 11 | {if="$formatter==='markdown'"} |
12 | <link type="text/css" rel="stylesheet" href="css/markdown.min.css?v={$version_hash}" /> | 12 | <link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" /> |
13 | {/if} | 13 | {/if} |
14 | {loop="$plugins_includes.css_files"} | 14 | {loop="$plugins_includes.css_files"} |
15 | <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}#"/> |
16 | {/loop} | 16 | {/loop} |
17 | {if="is_file('data/user.css')"} | 17 | {if="is_file('data/user.css')"} |
18 | <link type="text/css" rel="stylesheet" href="data/user.css#" /> | 18 | <link type="text/css" rel="stylesheet" href="{$base_path}/data/user.css#" /> |
19 | {/if} | 19 | {/if} |
20 | <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}" /> | ||
21 | {if="! empty($links) && count($links) === 1"} | 22 | {if="! empty($links) && count($links) === 1"} |
22 | {$link=reset($links)} | 23 | {$link=reset($links)} |
23 | <meta property="og:title" content="{$link.title}" /> | 24 | <meta property="og:title" content="{$link.title}" /> |
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..c7617b22 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="urlencode($value)"}" 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/{$value|urlencode}">{$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,12 @@ | |||
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}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a> |
274 | {/if} | 276 | {/if} |
275 | </div> | 277 | </div> |
276 | </div> | 278 | </div> |
@@ -295,6 +297,6 @@ | |||
295 | </div> | 297 | </div> |
296 | 298 | ||
297 | {include="page.footer"} | 299 | {include="page.footer"} |
298 | <script src="js/thumbnails.min.js?v={$version_hash}"></script> | 300 | <script src="{$asset_path}/js/thumbnails.min.js?v={$version_hash}#"></script> |
299 | </body> | 301 | </body> |
300 | </html> | 302 | </html> |
diff --git a/tpl/default/linklist.paging.html b/tpl/default/linklist.paging.html index 68947f92..7b320eaf 100644 --- a/tpl/default/linklist.paging.html +++ b/tpl/default/linklist.paging.html | |||
@@ -6,14 +6,14 @@ | |||
6 | {'Filters'|t} | 6 | {'Filters'|t} |
7 | </span> | 7 | </span> |
8 | {if="$is_logged_in"} | 8 | {if="$is_logged_in"} |
9 | <a href="?visibility=private" aria-label="{'Only display private links'|t}" title="{'Only display private links'|t}" | 9 | <a href="{$base_path}/admin/visibility/private" aria-label="{'Only display private links'|t}" title="{'Only display private links'|t}" |
10 | class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}" | 10 | class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}" |
11 | ><i class="fa fa-user-secret" aria-hidden="true"></i></a> | 11 | ><i class="fa fa-user-secret" aria-hidden="true"></i></a> |
12 | <a href="?visibility=public" aria-label="{'Only display public links'|t}" title="{'Only display public links'|t}" | 12 | <a href="{$base_path}/admin/visibility/public" aria-label="{'Only display public links'|t}" title="{'Only display public links'|t}" |
13 | class="{if="$visibility==='public'"}filter-on{else}filter-off{/if}" | 13 | class="{if="$visibility==='public'"}filter-on{else}filter-off{/if}" |
14 | ><i class="fa fa-globe" aria-hidden="true"></i></a> | 14 | ><i class="fa fa-globe" aria-hidden="true"></i></a> |
15 | {/if} | 15 | {/if} |
16 | <a href="?untaggedonly" aria-label="{'Filter untagged links'|t}" title="{'Filter untagged links'|t}" | 16 | <a href="{$base_path}/untagged-only" aria-label="{'Filter untagged links'|t}" title="{'Filter untagged links'|t}" |
17 | class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if} | 17 | class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if} |
18 | ><i class="fa fa-tag" aria-hidden="true"></i></a> | 18 | ><i class="fa fa-tag" aria-hidden="true"></i></a> |
19 | <a href="#" aria-label="{'Select all'|t}" title="{'Select all'|t}" | 19 | <a href="#" aria-label="{'Select all'|t}" title="{'Select all'|t}" |
@@ -53,11 +53,11 @@ | |||
53 | 53 | ||
54 | <div class="linksperpage pure-u-1-3"> | 54 | <div class="linksperpage pure-u-1-3"> |
55 | <div class="pure-u-0 pure-u-lg-visible">{'Links per page'|t}</div> | 55 | <div class="pure-u-0 pure-u-lg-visible">{'Links per page'|t}</div> |
56 | <a href="?linksperpage=20">20</a> | 56 | <a href="{$base_path}/links-per-page?nb=20">20</a> |
57 | <a href="?linksperpage=50">50</a> | 57 | <a href="{$base_path}/links-per-page?nb=50">50</a> |
58 | <a href="?linksperpage=100">100</a> | 58 | <a href="{$base_path}/links-per-page?nb=100">100</a> |
59 | <form method="GET" class="pure-u-0 pure-u-lg-visible"> | 59 | <form method="GET" class="pure-u-0 pure-u-lg-visible" action="{$base_path}/links-per-page"> |
60 | <input type="text" name="linksperpage" placeholder="133"> | 60 | <input type="text" name="nb" placeholder="133"> |
61 | </form> | 61 | </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}"> | 62 | <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> | 63 | <i class="fa fa-chevron-up" aria-hidden="true"></i> |
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 82f8ebf1..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="/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="/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 7839fcca..024882ec 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|urlencode}" 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> |
@@ -48,8 +48,8 @@ | |||
48 | 48 | ||
49 | <div id="cloudtag" class="cloudtag-container"> | 49 | <div id="cloudtag" class="cloudtag-container"> |
50 | {loop="tags"} | 50 | {loop="tags"} |
51 | <a href="?searchtags={$key|urlencode} {$search_tags|urlencode}" style="font-size:{$value.size}em;">{$key}</a | 51 | <a href="{$base_path}/?searchtags={$key|urlencode} {$search_tags|urlencode}" style="font-size:{$value.size}em;">{$key}</a |
52 | ><a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value.count}</a> | 52 | ><a href="{$base_path}/add-tag/{$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value.count}</a> |
53 | {loop="$value.tag_plugin"} | 53 | {loop="$value.tag_plugin"} |
54 | {$value} | 54 | {$value} |
55 | {/loop} | 55 | {/loop} |
diff --git a/tpl/default/tag.list.html b/tpl/default/tag.list.html index d5777465..99ae44d2 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|urlencode}" 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> |
@@ -51,13 +51,13 @@ | |||
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={$key|urlencode}" 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/{$key|urlencode}" 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={$key|urlencode} {$search_tags|urlencode}" 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..8718b188 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 5f9bef08..504644ca 100644 --- a/tpl/default/thumbnails.html +++ b/tpl/default/thumbnails.html | |||
@@ -43,6 +43,6 @@ | |||
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 53b0cad2..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> |
@@ -159,7 +159,7 @@ | |||
159 | {if="! $gd_enabled"} | 159 | {if="! $gd_enabled"} |
160 | {'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} |
161 | {elseif="$thumbnails_enabled"} | 161 | {elseif="$thumbnails_enabled"} |
162 | <a href="?do=thumbs_update">{'Synchonize thumbnails'|t}</a> | 162 | <a href="{$base_path}/admin/thumbnails">{'Synchonize thumbnails'|t}</a> |
163 | {/if} | 163 | {/if} |
164 | </label> | 164 | </label> |
165 | </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 6f7a330f..c3671b1f 100644 --- a/tpl/vintage/editlink.html +++ b/tpl/vintage/editlink.html | |||
@@ -1,20 +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}"> | 14 | <input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> |
19 | {if="isset($link.id)"} | 15 | {if="isset($link.id)"} |
20 | <input type="hidden" name="lf_id" value="{$link.id}"> | 16 | <input type="hidden" name="lf_id" value="{$link.id}"> |
@@ -48,19 +44,18 @@ | |||
48 | {/if} | 44 | {/if} |
49 | <input type="submit" value="Save" name="save_edit" class="bigbutton"> | 45 | <input type="submit" value="Save" name="save_edit" class="bigbutton"> |
50 | {if="!$link_is_new && isset($link.id)"} | 46 | {if="!$link_is_new && isset($link.id)"} |
51 | <a href="?delete_link&lf_linkdate={$link.id}&token={$token}" | 47 | <a href="{$base_path}/admin/shaare/delete?id={$link.id}&token={$token}" |
52 | name="delete_link" class="bigbutton" | 48 | name="delete_link" class="bigbutton" |
53 | onClick="return confirmDeleteLink();"> | 49 | onClick="return confirmDeleteLink();"> |
54 | {'Delete'|t} | 50 | {'Delete'|t} |
55 | </a> | 51 | </a> |
56 | {/if} | 52 | {/if} |
57 | <input type="hidden" name="token" value="{$token}"> | 53 | <input type="hidden" name="token" value="{$token}"> |
54 | <input type="hidden" name="source" value="{$source}"> | ||
58 | {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if} | 55 | {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if} |
59 | </form> | 56 | </form> |
60 | </div> | 57 | </div> |
61 | </div> | 58 | </div> |
62 | {if="$source !== 'firefoxsocialapi'"} | ||
63 | {include="page.footer"} | 59 | {include="page.footer"} |
64 | {/if} | ||
65 | </body> | 60 | </body> |
66 | </html> | 61 | </html> |
diff --git a/tpl/vintage/error.html b/tpl/vintage/error.html index b6e62be0..64f54cd2 100644 --- a/tpl/vintage/error.html +++ b/tpl/vintage/error.html | |||
@@ -18,7 +18,7 @@ | |||
18 | </pre> | 18 | </pre> |
19 | {/if} | 19 | {/if} |
20 | 20 | ||
21 | <p>Would you mind <a href="?">clicking here</a>?</p> | 21 | <p>Would you mind <a href="{$base_path}/">clicking here</a>?</p> |
22 | </div> | 22 | </div> |
23 | {include="page.footer"} | 23 | {include="page.footer"} |
24 | </body> | 24 | </body> |
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 0621cb9e..5919bb49 100644 --- a/tpl/vintage/feed.atom.html +++ b/tpl/vintage/feed.atom.html | |||
@@ -6,8 +6,8 @@ | |||
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} |
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 8d273c44..eac05701 100644 --- a/tpl/vintage/includes.html +++ b/tpl/vintage/includes.html | |||
@@ -3,18 +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'"} | 10 | {if="$formatter==='markdown'"} |
11 | <link type="text/css" rel="stylesheet" href="css/markdown.min.css?v={$version_hash}" /> | 11 | <link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" /> |
12 | {/if} | 12 | {/if} |
13 | {loop="$plugins_includes.css_files"} | 13 | {loop="$plugins_includes.css_files"} |
14 | <link type="text/css" rel="stylesheet" href="{$value}#"/> | 14 | <link type="text/css" rel="stylesheet" href="{$base_path}/{$value}#"/> |
15 | {/loop} | 15 | {/loop} |
16 | {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} |
17 | <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}" /> | ||
18 | {if="! empty($links) && count($links) === 1"} | 19 | {if="! empty($links) && count($links) === 1"} |
19 | {$link=reset($links)} | 20 | {$link=reset($links)} |
20 | <meta property="og:title" content="{$link.title}" /> | 21 | <meta property="og:title" content="{$link.title}" /> |
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..b9396df6 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,8 +23,13 @@ | |||
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 | <div class="paging_current">page {$page_current} / {$page_max} </div> |
diff --git a/tpl/vintage/loginform.html b/tpl/vintage/loginform.html index a3792066..6aa20ab1 100644 --- a/tpl/vintage/loginform.html +++ b/tpl/vintage/loginform.html | |||
@@ -11,7 +11,7 @@ | |||
11 | {include="page.header"} | 11 | {include="page.header"} |
12 | 12 | ||
13 | <div id="headerform"> | 13 | <div id="headerform"> |
14 | <form method="post" name="loginform"> | 14 | <form method="post" name="loginform" action="{$base_path}/login"> |
15 | <label for="login">Login: <input type="text" id="login" name="login" tabindex="1" | 15 | <label for="login">Login: <input type="text" id="login" name="login" tabindex="1" |
16 | {if="!empty($username)"}value="{$username}"{/if}> | 16 | {if="!empty($username)"}value="{$username}"{/if}> |
17 | </label> | 17 | </label> |
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 a37926d2..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="/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 5cad845b..18f296f7 100644 --- a/tpl/vintage/thumbnails.html +++ b/tpl/vintage/thumbnails.html | |||
@@ -23,6 +23,6 @@ | |||
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' |