diff options
30 files changed, 687 insertions, 190 deletions
@@ -26,7 +26,7 @@ RUN cd shaarli \ | |||
26 | 26 | ||
27 | # Stage 4: | 27 | # Stage 4: |
28 | # - Shaarli image | 28 | # - Shaarli image |
29 | FROM alpine:3.8 | 29 | FROM alpine:3.12 |
30 | LABEL maintainer="Shaarli Community" | 30 | LABEL maintainer="Shaarli Community" |
31 | 31 | ||
32 | RUN apk --update --no-cache add \ | 32 | RUN apk --update --no-cache add \ |
diff --git a/Dockerfile.armhf b/Dockerfile.armhf index 5bbf6680..471f2397 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf | |||
@@ -1,7 +1,7 @@ | |||
1 | # Stage 1: | 1 | # Stage 1: |
2 | # - Copy Shaarli sources | 2 | # - Copy Shaarli sources |
3 | # - Build documentation | 3 | # - Build documentation |
4 | FROM arm32v6/alpine:3.8 as docs | 4 | FROM arm32v6/alpine:3.10 as docs |
5 | ADD . /usr/src/app/shaarli | 5 | ADD . /usr/src/app/shaarli |
6 | RUN apk --update --no-cache add py2-pip \ | 6 | RUN apk --update --no-cache add py2-pip \ |
7 | && cd /usr/src/app/shaarli \ | 7 | && cd /usr/src/app/shaarli \ |
@@ -10,7 +10,7 @@ RUN apk --update --no-cache add py2-pip \ | |||
10 | 10 | ||
11 | # Stage 2: | 11 | # Stage 2: |
12 | # - Resolve PHP dependencies with Composer | 12 | # - Resolve PHP dependencies with Composer |
13 | FROM arm32v6/alpine:3.8 as composer | 13 | FROM arm32v6/alpine:3.10 as composer |
14 | COPY --from=docs /usr/src/app/shaarli /app/shaarli | 14 | COPY --from=docs /usr/src/app/shaarli /app/shaarli |
15 | RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer \ | 15 | RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer \ |
16 | && cd /app/shaarli \ | 16 | && cd /app/shaarli \ |
@@ -18,7 +18,7 @@ RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer | |||
18 | 18 | ||
19 | # Stage 3: | 19 | # Stage 3: |
20 | # - Frontend dependencies | 20 | # - Frontend dependencies |
21 | FROM arm32v6/alpine:3.8 as node | 21 | FROM arm32v6/alpine:3.10 as node |
22 | COPY --from=composer /app/shaarli /shaarli | 22 | COPY --from=composer /app/shaarli /shaarli |
23 | RUN apk --update --no-cache add yarn nodejs-current python2 build-base \ | 23 | RUN apk --update --no-cache add yarn nodejs-current python2 build-base \ |
24 | && cd /shaarli \ | 24 | && cd /shaarli \ |
@@ -28,7 +28,7 @@ RUN apk --update --no-cache add yarn nodejs-current python2 build-base \ | |||
28 | 28 | ||
29 | # Stage 4: | 29 | # Stage 4: |
30 | # - Shaarli image | 30 | # - Shaarli image |
31 | FROM arm32v6/alpine:3.8 | 31 | FROM arm32v6/alpine:3.10 |
32 | LABEL maintainer="Shaarli Community" | 32 | LABEL maintainer="Shaarli Community" |
33 | 33 | ||
34 | RUN apk --update --no-cache add \ | 34 | RUN apk --update --no-cache add \ |
diff --git a/application/bookmark/BookmarkIO.php b/application/bookmark/BookmarkIO.php index c78dbe41..8439d470 100644 --- a/application/bookmark/BookmarkIO.php +++ b/application/bookmark/BookmarkIO.php | |||
@@ -4,6 +4,7 @@ declare(strict_types=1); | |||
4 | 4 | ||
5 | namespace Shaarli\Bookmark; | 5 | namespace Shaarli\Bookmark; |
6 | 6 | ||
7 | use malkusch\lock\exception\LockAcquireException; | ||
7 | use malkusch\lock\mutex\Mutex; | 8 | use malkusch\lock\mutex\Mutex; |
8 | use malkusch\lock\mutex\NoMutex; | 9 | use malkusch\lock\mutex\NoMutex; |
9 | use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; | 10 | use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; |
@@ -80,7 +81,7 @@ class BookmarkIO | |||
80 | } | 81 | } |
81 | 82 | ||
82 | $content = null; | 83 | $content = null; |
83 | $this->mutex->synchronized(function () use (&$content) { | 84 | $this->synchronized(function () use (&$content) { |
84 | $content = file_get_contents($this->datastore); | 85 | $content = file_get_contents($this->datastore); |
85 | }); | 86 | }); |
86 | 87 | ||
@@ -119,11 +120,28 @@ class BookmarkIO | |||
119 | 120 | ||
120 | $data = self::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self::$phpSuffix; | 121 | $data = self::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self::$phpSuffix; |
121 | 122 | ||
122 | $this->mutex->synchronized(function () use ($data) { | 123 | $this->synchronized(function () use ($data) { |
123 | file_put_contents( | 124 | file_put_contents( |
124 | $this->datastore, | 125 | $this->datastore, |
125 | $data | 126 | $data |
126 | ); | 127 | ); |
127 | }); | 128 | }); |
128 | } | 129 | } |
130 | |||
131 | /** | ||
132 | * Wrapper applying mutex to provided function. | ||
133 | * If the lock can't be acquired (e.g. some shared hosting provider), we execute the function without mutex. | ||
134 | * | ||
135 | * @see https://github.com/shaarli/Shaarli/issues/1650 | ||
136 | * | ||
137 | * @param callable $function | ||
138 | */ | ||
139 | protected function synchronized(callable $function): void | ||
140 | { | ||
141 | try { | ||
142 | $this->mutex->synchronized($function); | ||
143 | } catch (LockAcquireException $exception) { | ||
144 | $function(); | ||
145 | } | ||
146 | } | ||
129 | } | 147 | } |
diff --git a/application/bookmark/LinkUtils.php b/application/bookmark/LinkUtils.php index d65e97ed..0ab2d213 100644 --- a/application/bookmark/LinkUtils.php +++ b/application/bookmark/LinkUtils.php | |||
@@ -68,11 +68,13 @@ function html_extract_tag($tag, $html) | |||
68 | $properties = implode('|', $propertiesKey); | 68 | $properties = implode('|', $propertiesKey); |
69 | // We need a OR here to accept either 'property=og:noquote' or 'property="og:unrelated og:my-tag"' | 69 | // We need a OR here to accept either 'property=og:noquote' or 'property="og:unrelated og:my-tag"' |
70 | $orCondition = '["\']?(?:og:)?' . $tag . '["\']?|["\'][^\'"]*?(?:og:)?' . $tag . '[^\'"]*?[\'"]'; | 70 | $orCondition = '["\']?(?:og:)?' . $tag . '["\']?|["\'][^\'"]*?(?:og:)?' . $tag . '[^\'"]*?[\'"]'; |
71 | // Support quotes in double quoted content, and the other way around | ||
72 | $content = 'content=(["\'])((?:(?!\1).)*)\1'; | ||
71 | // Try to retrieve OpenGraph tag. | 73 | // Try to retrieve OpenGraph tag. |
72 | $ogRegex = '#<meta[^>]+(?:' . $properties . ')=(?:' . $orCondition . ')[^>]*content=(["\'])([^\1]*?)\1.*?>#'; | 74 | $ogRegex = '#<meta[^>]+(?:' . $properties . ')=(?:' . $orCondition . ')[^>]*' . $content . '.*?>#'; |
73 | // If the attributes are not in the order property => content (e.g. Github) | 75 | // If the attributes are not in the order property => content (e.g. Github) |
74 | // New regex to keep this readable... more or less. | 76 | // New regex to keep this readable... more or less. |
75 | $ogRegexReverse = '#<meta[^>]+content=(["\'])([^\1]*?)\1[^>]+(?:' . $properties . ')=(?:' . $orCondition . ').*?>#'; | 77 | $ogRegexReverse = '#<meta[^>]+' . $content . '[^>]+(?:' . $properties . ')=(?:' . $orCondition . ').*?>#'; |
76 | 78 | ||
77 | if ( | 79 | if ( |
78 | preg_match($ogRegex, $html, $matches) > 0 | 80 | preg_match($ogRegex, $html, $matches) > 0 |
diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php index f0234eca..6d69a880 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php | |||
@@ -50,6 +50,9 @@ class ContainerBuilder | |||
50 | /** @var LoginManager */ | 50 | /** @var LoginManager */ |
51 | protected $login; | 51 | protected $login; |
52 | 52 | ||
53 | /** @var PluginManager */ | ||
54 | protected $pluginManager; | ||
55 | |||
53 | /** @var LoggerInterface */ | 56 | /** @var LoggerInterface */ |
54 | protected $logger; | 57 | protected $logger; |
55 | 58 | ||
@@ -61,12 +64,14 @@ class ContainerBuilder | |||
61 | SessionManager $session, | 64 | SessionManager $session, |
62 | CookieManager $cookieManager, | 65 | CookieManager $cookieManager, |
63 | LoginManager $login, | 66 | LoginManager $login, |
67 | PluginManager $pluginManager, | ||
64 | LoggerInterface $logger | 68 | LoggerInterface $logger |
65 | ) { | 69 | ) { |
66 | $this->conf = $conf; | 70 | $this->conf = $conf; |
67 | $this->session = $session; | 71 | $this->session = $session; |
68 | $this->login = $login; | 72 | $this->login = $login; |
69 | $this->cookieManager = $cookieManager; | 73 | $this->cookieManager = $cookieManager; |
74 | $this->pluginManager = $pluginManager; | ||
70 | $this->logger = $logger; | 75 | $this->logger = $logger; |
71 | } | 76 | } |
72 | 77 | ||
@@ -78,12 +83,10 @@ class ContainerBuilder | |||
78 | $container['sessionManager'] = $this->session; | 83 | $container['sessionManager'] = $this->session; |
79 | $container['cookieManager'] = $this->cookieManager; | 84 | $container['cookieManager'] = $this->cookieManager; |
80 | $container['loginManager'] = $this->login; | 85 | $container['loginManager'] = $this->login; |
86 | $container['pluginManager'] = $this->pluginManager; | ||
81 | $container['logger'] = $this->logger; | 87 | $container['logger'] = $this->logger; |
82 | $container['basePath'] = $this->basePath; | 88 | $container['basePath'] = $this->basePath; |
83 | 89 | ||
84 | $container['plugins'] = function (ShaarliContainer $container): PluginManager { | ||
85 | return new PluginManager($container->conf); | ||
86 | }; | ||
87 | 90 | ||
88 | $container['history'] = function (ShaarliContainer $container): History { | 91 | $container['history'] = function (ShaarliContainer $container): History { |
89 | return new History($container->conf->get('resource.history')); | 92 | return new History($container->conf->get('resource.history')); |
@@ -113,14 +116,6 @@ class ContainerBuilder | |||
113 | ); | 116 | ); |
114 | }; | 117 | }; |
115 | 118 | ||
116 | $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { | ||
117 | $pluginManager = new PluginManager($container->conf); | ||
118 | |||
119 | $pluginManager->load($container->conf->get('general.enabled_plugins')); | ||
120 | |||
121 | return $pluginManager; | ||
122 | }; | ||
123 | |||
124 | $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { | 119 | $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { |
125 | return new FormatterFactory( | 120 | return new FormatterFactory( |
126 | $container->conf, | 121 | $container->conf, |
diff --git a/application/feed/CachedPage.php b/application/feed/CachedPage.php index d809bdd9..c23c200f 100644 --- a/application/feed/CachedPage.php +++ b/application/feed/CachedPage.php | |||
@@ -1,34 +1,43 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | declare(strict_types=1); | ||
4 | |||
3 | namespace Shaarli\Feed; | 5 | namespace Shaarli\Feed; |
4 | 6 | ||
7 | use DatePeriod; | ||
8 | |||
5 | /** | 9 | /** |
6 | * Simple cache system, mainly for the RSS/ATOM feeds | 10 | * Simple cache system, mainly for the RSS/ATOM feeds |
7 | */ | 11 | */ |
8 | class CachedPage | 12 | class CachedPage |
9 | { | 13 | { |
10 | // Directory containing page caches | 14 | /** Directory containing page caches */ |
11 | private $cacheDir; | 15 | protected $cacheDir; |
16 | |||
17 | /** Should this URL be cached (boolean)? */ | ||
18 | protected $shouldBeCached; | ||
12 | 19 | ||
13 | // Should this URL be cached (boolean)? | 20 | /** Name of the cache file for this URL */ |
14 | private $shouldBeCached; | 21 | protected $filename; |
15 | 22 | ||
16 | // Name of the cache file for this URL | 23 | /** @var DatePeriod|null Optionally specify a period of time for cache validity */ |
17 | private $filename; | 24 | protected $validityPeriod; |
18 | 25 | ||
19 | /** | 26 | /** |
20 | * Creates a new CachedPage | 27 | * Creates a new CachedPage |
21 | * | 28 | * |
22 | * @param string $cacheDir page cache directory | 29 | * @param string $cacheDir page cache directory |
23 | * @param string $url page URL | 30 | * @param string $url page URL |
24 | * @param bool $shouldBeCached whether this page needs to be cached | 31 | * @param bool $shouldBeCached whether this page needs to be cached |
32 | * @param ?DatePeriod $validityPeriod Optionally specify a time limit on requested cache | ||
25 | */ | 33 | */ |
26 | public function __construct($cacheDir, $url, $shouldBeCached) | 34 | public function __construct($cacheDir, $url, $shouldBeCached, ?DatePeriod $validityPeriod) |
27 | { | 35 | { |
28 | // TODO: check write access to the cache directory | 36 | // TODO: check write access to the cache directory |
29 | $this->cacheDir = $cacheDir; | 37 | $this->cacheDir = $cacheDir; |
30 | $this->filename = $this->cacheDir . '/' . sha1($url) . '.cache'; | 38 | $this->filename = $this->cacheDir . '/' . sha1($url) . '.cache'; |
31 | $this->shouldBeCached = $shouldBeCached; | 39 | $this->shouldBeCached = $shouldBeCached; |
40 | $this->validityPeriod = $validityPeriod; | ||
32 | } | 41 | } |
33 | 42 | ||
34 | /** | 43 | /** |
@@ -41,10 +50,20 @@ class CachedPage | |||
41 | if (!$this->shouldBeCached) { | 50 | if (!$this->shouldBeCached) { |
42 | return null; | 51 | return null; |
43 | } | 52 | } |
44 | if (is_file($this->filename)) { | 53 | if (!is_file($this->filename)) { |
45 | return file_get_contents($this->filename); | 54 | return null; |
55 | } | ||
56 | if ($this->validityPeriod !== null) { | ||
57 | $cacheDate = \DateTime::createFromFormat('U', (string) filemtime($this->filename)); | ||
58 | if ( | ||
59 | $cacheDate < $this->validityPeriod->getStartDate() | ||
60 | || $cacheDate > $this->validityPeriod->getEndDate() | ||
61 | ) { | ||
62 | return null; | ||
63 | } | ||
46 | } | 64 | } |
47 | return null; | 65 | |
66 | return file_get_contents($this->filename); | ||
48 | } | 67 | } |
49 | 68 | ||
50 | /** | 69 | /** |
diff --git a/application/front/controller/admin/ServerController.php b/application/front/controller/admin/ServerController.php index fabeaf2f..4b74f4a9 100644 --- a/application/front/controller/admin/ServerController.php +++ b/application/front/controller/admin/ServerController.php | |||
@@ -39,11 +39,16 @@ class ServerController extends ShaarliAdminController | |||
39 | $currentVersion = $currentVersion === 'dev' ? $currentVersion : 'v' . $currentVersion; | 39 | $currentVersion = $currentVersion === 'dev' ? $currentVersion : 'v' . $currentVersion; |
40 | $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); | 40 | $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); |
41 | 41 | ||
42 | $permissions = array_merge( | ||
43 | ApplicationUtils::checkResourcePermissions($this->container->conf), | ||
44 | ApplicationUtils::checkDatastoreMutex() | ||
45 | ); | ||
46 | |||
42 | $this->assignView('php_version', PHP_VERSION); | 47 | $this->assignView('php_version', PHP_VERSION); |
43 | $this->assignView('php_eol', format_date($phpEol, false)); | 48 | $this->assignView('php_eol', format_date($phpEol, false)); |
44 | $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); | 49 | $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); |
45 | $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); | 50 | $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); |
46 | $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); | 51 | $this->assignView('permissions', $permissions); |
47 | $this->assignView('release_url', $releaseUrl); | 52 | $this->assignView('release_url', $releaseUrl); |
48 | $this->assignView('latest_version', $latestVersion); | 53 | $this->assignView('latest_version', $latestVersion); |
49 | $this->assignView('current_version', $currentVersion); | 54 | $this->assignView('current_version', $currentVersion); |
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 846cfe22..29492a5f 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php | |||
@@ -86,9 +86,11 @@ class DailyController extends ShaarliVisitorController | |||
86 | public function rss(Request $request, Response $response): Response | 86 | public function rss(Request $request, Response $response): Response |
87 | { | 87 | { |
88 | $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8'); | 88 | $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8'); |
89 | $type = DailyPageHelper::extractRequestedType($request); | ||
90 | $cacheDuration = DailyPageHelper::getCacheDatePeriodByType($type); | ||
89 | 91 | ||
90 | $pageUrl = page_url($this->container->environment); | 92 | $pageUrl = page_url($this->container->environment); |
91 | $cache = $this->container->pageCacheManager->getCachePage($pageUrl); | 93 | $cache = $this->container->pageCacheManager->getCachePage($pageUrl, $cacheDuration); |
92 | 94 | ||
93 | $cached = $cache->cachedVersion(); | 95 | $cached = $cache->cachedVersion(); |
94 | if (!empty($cached)) { | 96 | if (!empty($cached)) { |
@@ -96,7 +98,6 @@ class DailyController extends ShaarliVisitorController | |||
96 | } | 98 | } |
97 | 99 | ||
98 | $days = []; | 100 | $days = []; |
99 | $type = DailyPageHelper::extractRequestedType($request); | ||
100 | $format = DailyPageHelper::getFormatByType($type); | 101 | $format = DailyPageHelper::getFormatByType($type); |
101 | $length = DailyPageHelper::getRssLengthByType($type); | 102 | $length = DailyPageHelper::getRssLengthByType($type); |
102 | foreach ($this->container->bookmarkService->search() as $bookmark) { | 103 | foreach ($this->container->bookmarkService->search() as $bookmark) { |
@@ -131,7 +132,7 @@ class DailyController extends ShaarliVisitorController | |||
131 | $dataPerDay[$day] = [ | 132 | $dataPerDay[$day] = [ |
132 | 'date' => $endDateTime, | 133 | 'date' => $endDateTime, |
133 | 'date_rss' => $endDateTime->format(DateTime::RSS), | 134 | 'date_rss' => $endDateTime->format(DateTime::RSS), |
134 | 'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime), | 135 | 'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime, false), |
135 | 'absolute_url' => $indexUrl . 'daily?' . $type . '=' . $day, | 136 | 'absolute_url' => $indexUrl . 'daily?' . $type . '=' . $day, |
136 | 'links' => [], | 137 | 'links' => [], |
137 | ]; | 138 | ]; |
diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php index bf965929..418d4a49 100644 --- a/application/front/controller/visitor/InstallController.php +++ b/application/front/controller/visitor/InstallController.php | |||
@@ -56,11 +56,16 @@ class InstallController extends ShaarliVisitorController | |||
56 | 56 | ||
57 | $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); | 57 | $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); |
58 | 58 | ||
59 | $permissions = array_merge( | ||
60 | ApplicationUtils::checkResourcePermissions($this->container->conf), | ||
61 | ApplicationUtils::checkDatastoreMutex() | ||
62 | ); | ||
63 | |||
59 | $this->assignView('php_version', PHP_VERSION); | 64 | $this->assignView('php_version', PHP_VERSION); |
60 | $this->assignView('php_eol', format_date($phpEol, false)); | 65 | $this->assignView('php_eol', format_date($phpEol, false)); |
61 | $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); | 66 | $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); |
62 | $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); | 67 | $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); |
63 | $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); | 68 | $this->assignView('permissions', $permissions); |
64 | 69 | ||
65 | $this->assignView('pagetitle', t('Install Shaarli')); | 70 | $this->assignView('pagetitle', t('Install Shaarli')); |
66 | 71 | ||
diff --git a/application/helper/ApplicationUtils.php b/application/helper/ApplicationUtils.php index 212dd8e2..a6c03aae 100644 --- a/application/helper/ApplicationUtils.php +++ b/application/helper/ApplicationUtils.php | |||
@@ -3,6 +3,8 @@ | |||
3 | namespace Shaarli\Helper; | 3 | namespace Shaarli\Helper; |
4 | 4 | ||
5 | use Exception; | 5 | use Exception; |
6 | use malkusch\lock\exception\LockAcquireException; | ||
7 | use malkusch\lock\mutex\FlockMutex; | ||
6 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
7 | 9 | ||
8 | /** | 10 | /** |
@@ -252,6 +254,20 @@ class ApplicationUtils | |||
252 | return $errors; | 254 | return $errors; |
253 | } | 255 | } |
254 | 256 | ||
257 | public static function checkDatastoreMutex(): array | ||
258 | { | ||
259 | $mutex = new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2); | ||
260 | try { | ||
261 | $mutex->synchronized(function () { | ||
262 | return true; | ||
263 | }); | ||
264 | } catch (LockAcquireException $e) { | ||
265 | $errors[] = t('Lock can not be acquired on the datastore. You might encounter concurrent access issues.'); | ||
266 | } | ||
267 | |||
268 | return $errors ?? []; | ||
269 | } | ||
270 | |||
255 | /** | 271 | /** |
256 | * Returns a salted hash representing the current Shaarli version. | 272 | * Returns a salted hash representing the current Shaarli version. |
257 | * | 273 | * |
diff --git a/application/helper/DailyPageHelper.php b/application/helper/DailyPageHelper.php index 5fabc907..05f95812 100644 --- a/application/helper/DailyPageHelper.php +++ b/application/helper/DailyPageHelper.php | |||
@@ -4,6 +4,9 @@ declare(strict_types=1); | |||
4 | 4 | ||
5 | namespace Shaarli\Helper; | 5 | namespace Shaarli\Helper; |
6 | 6 | ||
7 | use DatePeriod; | ||
8 | use DateTimeImmutable; | ||
9 | use Exception; | ||
7 | use Shaarli\Bookmark\Bookmark; | 10 | use Shaarli\Bookmark\Bookmark; |
8 | use Slim\Http\Request; | 11 | use Slim\Http\Request; |
9 | 12 | ||
@@ -40,31 +43,31 @@ class DailyPageHelper | |||
40 | * @param string|null $requestedDate Input string extracted from the request | 43 | * @param string|null $requestedDate Input string extracted from the request |
41 | * @param Bookmark|null $latestBookmark Latest bookmark found in the datastore (by date) | 44 | * @param Bookmark|null $latestBookmark Latest bookmark found in the datastore (by date) |
42 | * | 45 | * |
43 | * @return \DateTimeImmutable from input or latest bookmark. | 46 | * @return DateTimeImmutable from input or latest bookmark. |
44 | * | 47 | * |
45 | * @throws \Exception Type not supported. | 48 | * @throws Exception Type not supported. |
46 | */ | 49 | */ |
47 | public static function extractRequestedDateTime( | 50 | public static function extractRequestedDateTime( |
48 | string $type, | 51 | string $type, |
49 | ?string $requestedDate, | 52 | ?string $requestedDate, |
50 | Bookmark $latestBookmark = null | 53 | Bookmark $latestBookmark = null |
51 | ): \DateTimeImmutable { | 54 | ): DateTimeImmutable { |
52 | $format = static::getFormatByType($type); | 55 | $format = static::getFormatByType($type); |
53 | if (empty($requestedDate)) { | 56 | if (empty($requestedDate)) { |
54 | return $latestBookmark instanceof Bookmark | 57 | return $latestBookmark instanceof Bookmark |
55 | ? new \DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM)) | 58 | ? new DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM)) |
56 | : new \DateTimeImmutable() | 59 | : new DateTimeImmutable() |
57 | ; | 60 | ; |
58 | } | 61 | } |
59 | 62 | ||
60 | // W is not supported by createFromFormat... | 63 | // W is not supported by createFromFormat... |
61 | if ($type === static::WEEK) { | 64 | if ($type === static::WEEK) { |
62 | return (new \DateTimeImmutable()) | 65 | return (new DateTimeImmutable()) |
63 | ->setISODate((int) substr($requestedDate, 0, 4), (int) substr($requestedDate, 4, 2)) | 66 | ->setISODate((int) substr($requestedDate, 0, 4), (int) substr($requestedDate, 4, 2)) |
64 | ; | 67 | ; |
65 | } | 68 | } |
66 | 69 | ||
67 | return \DateTimeImmutable::createFromFormat($format, $requestedDate); | 70 | return DateTimeImmutable::createFromFormat($format, $requestedDate); |
68 | } | 71 | } |
69 | 72 | ||
70 | /** | 73 | /** |
@@ -80,7 +83,7 @@ class DailyPageHelper | |||
80 | * | 83 | * |
81 | * @see https://www.php.net/manual/en/datetime.format.php | 84 | * @see https://www.php.net/manual/en/datetime.format.php |
82 | * | 85 | * |
83 | * @throws \Exception Type not supported. | 86 | * @throws Exception Type not supported. |
84 | */ | 87 | */ |
85 | public static function getFormatByType(string $type): string | 88 | public static function getFormatByType(string $type): string |
86 | { | 89 | { |
@@ -92,7 +95,7 @@ class DailyPageHelper | |||
92 | case static::DAY: | 95 | case static::DAY: |
93 | return 'Ymd'; | 96 | return 'Ymd'; |
94 | default: | 97 | default: |
95 | throw new \Exception('Unsupported daily format type'); | 98 | throw new Exception('Unsupported daily format type'); |
96 | } | 99 | } |
97 | } | 100 | } |
98 | 101 | ||
@@ -102,14 +105,14 @@ class DailyPageHelper | |||
102 | * and we don't want to alter original datetime. | 105 | * and we don't want to alter original datetime. |
103 | * | 106 | * |
104 | * @param string $type month/week/day | 107 | * @param string $type month/week/day |
105 | * @param \DateTimeImmutable $requested DateTime extracted from request input | 108 | * @param DateTimeImmutable $requested DateTime extracted from request input |
106 | * (should come from extractRequestedDateTime) | 109 | * (should come from extractRequestedDateTime) |
107 | * | 110 | * |
108 | * @return \DateTimeInterface First DateTime of the time period | 111 | * @return \DateTimeInterface First DateTime of the time period |
109 | * | 112 | * |
110 | * @throws \Exception Type not supported. | 113 | * @throws Exception Type not supported. |
111 | */ | 114 | */ |
112 | public static function getStartDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface | 115 | public static function getStartDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface |
113 | { | 116 | { |
114 | switch ($type) { | 117 | switch ($type) { |
115 | case static::MONTH: | 118 | case static::MONTH: |
@@ -119,7 +122,7 @@ class DailyPageHelper | |||
119 | case static::DAY: | 122 | case static::DAY: |
120 | return $requested->modify('Today midnight'); | 123 | return $requested->modify('Today midnight'); |
121 | default: | 124 | default: |
122 | throw new \Exception('Unsupported daily format type'); | 125 | throw new Exception('Unsupported daily format type'); |
123 | } | 126 | } |
124 | } | 127 | } |
125 | 128 | ||
@@ -129,14 +132,14 @@ class DailyPageHelper | |||
129 | * and we don't want to alter original datetime. | 132 | * and we don't want to alter original datetime. |
130 | * | 133 | * |
131 | * @param string $type month/week/day | 134 | * @param string $type month/week/day |
132 | * @param \DateTimeImmutable $requested DateTime extracted from request input | 135 | * @param DateTimeImmutable $requested DateTime extracted from request input |
133 | * (should come from extractRequestedDateTime) | 136 | * (should come from extractRequestedDateTime) |
134 | * | 137 | * |
135 | * @return \DateTimeInterface Last DateTime of the time period | 138 | * @return \DateTimeInterface Last DateTime of the time period |
136 | * | 139 | * |
137 | * @throws \Exception Type not supported. | 140 | * @throws Exception Type not supported. |
138 | */ | 141 | */ |
139 | public static function getEndDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface | 142 | public static function getEndDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface |
140 | { | 143 | { |
141 | switch ($type) { | 144 | switch ($type) { |
142 | case static::MONTH: | 145 | case static::MONTH: |
@@ -146,7 +149,7 @@ class DailyPageHelper | |||
146 | case static::DAY: | 149 | case static::DAY: |
147 | return $requested->modify('Today 23:59:59'); | 150 | return $requested->modify('Today 23:59:59'); |
148 | default: | 151 | default: |
149 | throw new \Exception('Unsupported daily format type'); | 152 | throw new Exception('Unsupported daily format type'); |
150 | } | 153 | } |
151 | } | 154 | } |
152 | 155 | ||
@@ -154,16 +157,20 @@ class DailyPageHelper | |||
154 | * Get localized description of the time period depending on given datetime and type. | 157 | * Get localized description of the time period depending on given datetime and type. |
155 | * Example: for a month period, it returns `October, 2020`. | 158 | * Example: for a month period, it returns `October, 2020`. |
156 | * | 159 | * |
157 | * @param string $type month/week/day | 160 | * @param string $type month/week/day |
158 | * @param \DateTimeImmutable $requested DateTime extracted from request input | 161 | * @param \DateTimeImmutable $requested DateTime extracted from request input |
159 | * (should come from extractRequestedDateTime) | 162 | * (should come from extractRequestedDateTime) |
163 | * @param bool $includeRelative Include relative date description (today, yesterday, etc.) | ||
160 | * | 164 | * |
161 | * @return string Localized time period description | 165 | * @return string Localized time period description |
162 | * | 166 | * |
163 | * @throws \Exception Type not supported. | 167 | * @throws Exception Type not supported. |
164 | */ | 168 | */ |
165 | public static function getDescriptionByType(string $type, \DateTimeImmutable $requested): string | 169 | public static function getDescriptionByType( |
166 | { | 170 | string $type, |
171 | \DateTimeImmutable $requested, | ||
172 | bool $includeRelative = true | ||
173 | ): string { | ||
167 | switch ($type) { | 174 | switch ($type) { |
168 | case static::MONTH: | 175 | case static::MONTH: |
169 | return $requested->format('F') . ', ' . $requested->format('Y'); | 176 | return $requested->format('F') . ', ' . $requested->format('Y'); |
@@ -172,14 +179,14 @@ class DailyPageHelper | |||
172 | return t('Week') . ' ' . $requested->format('W') . ' (' . format_date($requested, false) . ')'; | 179 | return t('Week') . ' ' . $requested->format('W') . ' (' . format_date($requested, false) . ')'; |
173 | case static::DAY: | 180 | case static::DAY: |
174 | $out = ''; | 181 | $out = ''; |
175 | if ($requested->format('Ymd') === date('Ymd')) { | 182 | if ($includeRelative && $requested->format('Ymd') === date('Ymd')) { |
176 | $out = t('Today') . ' - '; | 183 | $out = t('Today') . ' - '; |
177 | } elseif ($requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) { | 184 | } elseif ($includeRelative && $requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) { |
178 | $out = t('Yesterday') . ' - '; | 185 | $out = t('Yesterday') . ' - '; |
179 | } | 186 | } |
180 | return $out . format_date($requested, false); | 187 | return $out . format_date($requested, false); |
181 | default: | 188 | default: |
182 | throw new \Exception('Unsupported daily format type'); | 189 | throw new Exception('Unsupported daily format type'); |
183 | } | 190 | } |
184 | } | 191 | } |
185 | 192 | ||
@@ -190,7 +197,7 @@ class DailyPageHelper | |||
190 | * | 197 | * |
191 | * @return int number of elements | 198 | * @return int number of elements |
192 | * | 199 | * |
193 | * @throws \Exception Type not supported. | 200 | * @throws Exception Type not supported. |
194 | */ | 201 | */ |
195 | public static function getRssLengthByType(string $type): int | 202 | public static function getRssLengthByType(string $type): int |
196 | { | 203 | { |
@@ -202,7 +209,28 @@ class DailyPageHelper | |||
202 | case static::DAY: | 209 | case static::DAY: |
203 | return 30; // ~1 month | 210 | return 30; // ~1 month |
204 | default: | 211 | default: |
205 | throw new \Exception('Unsupported daily format type'); | 212 | throw new Exception('Unsupported daily format type'); |
206 | } | 213 | } |
207 | } | 214 | } |
215 | |||
216 | /** | ||
217 | * Get the number of items to display in the RSS feed depending on the given type. | ||
218 | * | ||
219 | * @param string $type month/week/day | ||
220 | * @param ?DateTimeImmutable $requested Currently only used for UT | ||
221 | * | ||
222 | * @return DatePeriod number of elements | ||
223 | * | ||
224 | * @throws Exception Type not supported. | ||
225 | */ | ||
226 | public static function getCacheDatePeriodByType(string $type, DateTimeImmutable $requested = null): DatePeriod | ||
227 | { | ||
228 | $requested = $requested ?? new DateTimeImmutable(); | ||
229 | |||
230 | return new DatePeriod( | ||
231 | static::getStartDateTimeByType($type, $requested), | ||
232 | new \DateInterval('P1D'), | ||
233 | static::getEndDateTimeByType($type, $requested) | ||
234 | ); | ||
235 | } | ||
208 | } | 236 | } |
diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php index 3ea55728..7fc0cb04 100644 --- a/application/plugin/PluginManager.php +++ b/application/plugin/PluginManager.php | |||
@@ -4,6 +4,7 @@ namespace Shaarli\Plugin; | |||
4 | 4 | ||
5 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
6 | use Shaarli\Plugin\Exception\PluginFileNotFoundException; | 6 | use Shaarli\Plugin\Exception\PluginFileNotFoundException; |
7 | use Shaarli\Plugin\Exception\PluginInvalidRouteException; | ||
7 | 8 | ||
8 | /** | 9 | /** |
9 | * Class PluginManager | 10 | * Class PluginManager |
@@ -26,6 +27,14 @@ class PluginManager | |||
26 | */ | 27 | */ |
27 | private $loadedPlugins = []; | 28 | private $loadedPlugins = []; |
28 | 29 | ||
30 | /** @var array List of registered routes. Contains keys: | ||
31 | * - `method`: HTTP method, GET/POST/PUT/PATCH/DELETE | ||
32 | * - `route` (path): without prefix, e.g. `/up/{variable}` | ||
33 | * It will be later prefixed by `/plugin/<plugin name>/`. | ||
34 | * - `callable` string, function name or FQN class's method, e.g. `demo_plugin_custom_controller`. | ||
35 | */ | ||
36 | protected $registeredRoutes = []; | ||
37 | |||
29 | /** | 38 | /** |
30 | * @var ConfigManager Configuration Manager instance. | 39 | * @var ConfigManager Configuration Manager instance. |
31 | */ | 40 | */ |
@@ -86,6 +95,9 @@ class PluginManager | |||
86 | $this->loadPlugin($dirs[$index], $plugin); | 95 | $this->loadPlugin($dirs[$index], $plugin); |
87 | } catch (PluginFileNotFoundException $e) { | 96 | } catch (PluginFileNotFoundException $e) { |
88 | error_log($e->getMessage()); | 97 | error_log($e->getMessage()); |
98 | } catch (\Throwable $e) { | ||
99 | $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage(); | ||
100 | $this->errors = array_unique(array_merge($this->errors, [$error])); | ||
89 | } | 101 | } |
90 | } | 102 | } |
91 | } | 103 | } |
@@ -166,6 +178,22 @@ class PluginManager | |||
166 | } | 178 | } |
167 | } | 179 | } |
168 | 180 | ||
181 | $registerRouteFunction = $pluginName . '_register_routes'; | ||
182 | $routes = null; | ||
183 | if (function_exists($registerRouteFunction)) { | ||
184 | $routes = call_user_func($registerRouteFunction); | ||
185 | } | ||
186 | |||
187 | if ($routes !== null) { | ||
188 | foreach ($routes as $route) { | ||
189 | if (static::validateRouteRegistration($route)) { | ||
190 | $this->registeredRoutes[$pluginName][] = $route; | ||
191 | } else { | ||
192 | throw new PluginInvalidRouteException($pluginName); | ||
193 | } | ||
194 | } | ||
195 | } | ||
196 | |||
169 | $this->loadedPlugins[] = $pluginName; | 197 | $this->loadedPlugins[] = $pluginName; |
170 | } | 198 | } |
171 | 199 | ||
@@ -238,6 +266,14 @@ class PluginManager | |||
238 | } | 266 | } |
239 | 267 | ||
240 | /** | 268 | /** |
269 | * @return array List of registered custom routes by plugins. | ||
270 | */ | ||
271 | public function getRegisteredRoutes(): array | ||
272 | { | ||
273 | return $this->registeredRoutes; | ||
274 | } | ||
275 | |||
276 | /** | ||
241 | * Return the list of encountered errors. | 277 | * Return the list of encountered errors. |
242 | * | 278 | * |
243 | * @return array List of errors (empty array if none exists). | 279 | * @return array List of errors (empty array if none exists). |
@@ -246,4 +282,32 @@ class PluginManager | |||
246 | { | 282 | { |
247 | return $this->errors; | 283 | return $this->errors; |
248 | } | 284 | } |
285 | |||
286 | /** | ||
287 | * Checks whether provided input is valid to register a new route. | ||
288 | * It must contain keys `method`, `route`, `callable` (all strings). | ||
289 | * | ||
290 | * @param string[] $input | ||
291 | * | ||
292 | * @return bool | ||
293 | */ | ||
294 | protected static function validateRouteRegistration(array $input): bool | ||
295 | { | ||
296 | if ( | ||
297 | !array_key_exists('method', $input) | ||
298 | || !in_array(strtoupper($input['method']), ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) | ||
299 | ) { | ||
300 | return false; | ||
301 | } | ||
302 | |||
303 | if (!array_key_exists('route', $input) || !preg_match('#^[a-z\d/\.\-_]+$#', $input['route'])) { | ||
304 | return false; | ||
305 | } | ||
306 | |||
307 | if (!array_key_exists('callable', $input)) { | ||
308 | return false; | ||
309 | } | ||
310 | |||
311 | return true; | ||
312 | } | ||
249 | } | 313 | } |
diff --git a/application/plugin/exception/PluginInvalidRouteException.php b/application/plugin/exception/PluginInvalidRouteException.php new file mode 100644 index 00000000..6ba9bc43 --- /dev/null +++ b/application/plugin/exception/PluginInvalidRouteException.php | |||
@@ -0,0 +1,26 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Plugin\Exception; | ||
6 | |||
7 | use Exception; | ||
8 | |||
9 | /** | ||
10 | * Class PluginFileNotFoundException | ||
11 | * | ||
12 | * Raise when plugin files can't be found. | ||
13 | */ | ||
14 | class PluginInvalidRouteException extends Exception | ||
15 | { | ||
16 | /** | ||
17 | * Construct exception with plugin name. | ||
18 | * Generate message. | ||
19 | * | ||
20 | * @param string $pluginName name of the plugin not found | ||
21 | */ | ||
22 | public function __construct() | ||
23 | { | ||
24 | $this->message = 'trying to register invalid route.'; | ||
25 | } | ||
26 | } | ||
diff --git a/application/render/PageCacheManager.php b/application/render/PageCacheManager.php index 97805c35..fe74bf27 100644 --- a/application/render/PageCacheManager.php +++ b/application/render/PageCacheManager.php | |||
@@ -2,6 +2,7 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Render; | 3 | namespace Shaarli\Render; |
4 | 4 | ||
5 | use DatePeriod; | ||
5 | use Shaarli\Feed\CachedPage; | 6 | use Shaarli\Feed\CachedPage; |
6 | 7 | ||
7 | /** | 8 | /** |
@@ -49,12 +50,21 @@ class PageCacheManager | |||
49 | $this->purgeCachedPages(); | 50 | $this->purgeCachedPages(); |
50 | } | 51 | } |
51 | 52 | ||
52 | public function getCachePage(string $pageUrl): CachedPage | 53 | /** |
54 | * Get CachedPage instance for provided URL. | ||
55 | * | ||
56 | * @param string $pageUrl | ||
57 | * @param ?DatePeriod $validityPeriod Optionally specify a time limit on requested cache | ||
58 | * | ||
59 | * @return CachedPage | ||
60 | */ | ||
61 | public function getCachePage(string $pageUrl, DatePeriod $validityPeriod = null): CachedPage | ||
53 | { | 62 | { |
54 | return new CachedPage( | 63 | return new CachedPage( |
55 | $this->pageCacheDir, | 64 | $this->pageCacheDir, |
56 | $pageUrl, | 65 | $pageUrl, |
57 | false === $this->isLoggedIn | 66 | false === $this->isLoggedIn, |
67 | $validityPeriod | ||
58 | ); | 68 | ); |
59 | } | 69 | } |
60 | } | 70 | } |
diff --git a/doc/md/REST-API.md b/doc/md/REST-API.md index 01071d8e..2a36ea29 100644 --- a/doc/md/REST-API.md +++ b/doc/md/REST-API.md | |||
@@ -73,7 +73,7 @@ var_dump(getInfo($baseUrl, $secret)); | |||
73 | ### Authentication | 73 | ### Authentication |
74 | 74 | ||
75 | - All requests to Shaarli's API must include a **JWT token** to verify their authenticity. | 75 | - All requests to Shaarli's API must include a **JWT token** to verify their authenticity. |
76 | - This token must be included as an HTTP header called `Authentication: Bearer <jwt token>`. | 76 | - This token must be included as an HTTP header called `Authorization: Bearer <jwt token>`. |
77 | - JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64: | 77 | - JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64: |
78 | 78 | ||
79 | ``` | 79 | ``` |
diff --git a/doc/md/dev/Plugin-system.md b/doc/md/dev/Plugin-system.md index f09fadc2..79654011 100644 --- a/doc/md/dev/Plugin-system.md +++ b/doc/md/dev/Plugin-system.md | |||
@@ -139,6 +139,31 @@ Each file contain two keys: | |||
139 | 139 | ||
140 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. | 140 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. |
141 | 141 | ||
142 | ### Register plugin's routes | ||
143 | |||
144 | Shaarli lets you register custom Slim routes for your plugin. | ||
145 | |||
146 | To register a route, the plugin must include a function called `function <plugin_name>_register_routes(): array`. | ||
147 | |||
148 | This method must return an array of routes, each entry must contain the following keys: | ||
149 | |||
150 | - `method`: HTTP method, `GET/POST/PUT/PATCH/DELETE` | ||
151 | - `route` (path): without prefix, e.g. `/up/{variable}` | ||
152 | It will be later prefixed by `/plugin/<plugin name>/`. | ||
153 | - `callable` string, function name or FQN class's method to execute, e.g. `demo_plugin_custom_controller`. | ||
154 | |||
155 | Callable functions or methods must have `Slim\Http\Request` and `Slim\Http\Response` parameters | ||
156 | and return a `Slim\Http\Response`. We recommend creating a dedicated class and extend either | ||
157 | `ShaarliVisitorController` or `ShaarliAdminController` to use helper functions they provide. | ||
158 | |||
159 | A dedicated plugin template is available for rendering content: `pluginscontent.html` using `content` placeholder. | ||
160 | |||
161 | > **Warning**: plugins are not able to use RainTPL template engine for their content due to technical restrictions. | ||
162 | > RainTPL does not allow to register multiple template folders, so all HTML rendering must be done within plugin | ||
163 | > custom controller. | ||
164 | |||
165 | Check out the `demo_plugin` for a live example: `GET <shaarli_url>/plugin/demo_plugin/custom`. | ||
166 | |||
142 | ### Understanding relative paths | 167 | ### Understanding relative paths |
143 | 168 | ||
144 | Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder. | 169 | Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder. |
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index 26dede4e..01492af4 100644 --- a/inc/languages/fr/LC_MESSAGES/shaarli.po +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po | |||
@@ -1,8 +1,8 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: Shaarli\n" | 3 | "Project-Id-Version: Shaarli\n" |
4 | "POT-Creation-Date: 2020-11-09 14:39+0100\n" | 4 | "POT-Creation-Date: 2020-11-24 13:13+0100\n" |
5 | "PO-Revision-Date: 2020-11-09 14:42+0100\n" | 5 | "PO-Revision-Date: 2020-11-24 13:14+0100\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" |
@@ -20,31 +20,31 @@ msgstr "" | |||
20 | "X-Poedit-SearchPath-3: init.php\n" | 20 | "X-Poedit-SearchPath-3: init.php\n" |
21 | "X-Poedit-SearchPath-4: plugins\n" | 21 | "X-Poedit-SearchPath-4: plugins\n" |
22 | 22 | ||
23 | #: application/History.php:180 | 23 | #: application/History.php:181 |
24 | msgid "History file isn't readable or writable" | 24 | msgid "History file isn't readable or writable" |
25 | msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" | 25 | msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" |
26 | 26 | ||
27 | #: application/History.php:191 | 27 | #: application/History.php:192 |
28 | msgid "Could not parse history file" | 28 | msgid "Could not parse history file" |
29 | msgstr "Format incorrect pour le fichier d'historique" | 29 | msgstr "Format incorrect pour le fichier d'historique" |
30 | 30 | ||
31 | #: application/Languages.php:181 | 31 | #: application/Languages.php:184 |
32 | msgid "Automatic" | 32 | msgid "Automatic" |
33 | msgstr "Automatique" | 33 | msgstr "Automatique" |
34 | 34 | ||
35 | #: application/Languages.php:182 | 35 | #: application/Languages.php:185 |
36 | msgid "German" | 36 | msgid "German" |
37 | msgstr "Allemand" | 37 | msgstr "Allemand" |
38 | 38 | ||
39 | #: application/Languages.php:183 | 39 | #: application/Languages.php:186 |
40 | msgid "English" | 40 | msgid "English" |
41 | msgstr "Anglais" | 41 | msgstr "Anglais" |
42 | 42 | ||
43 | #: application/Languages.php:184 | 43 | #: application/Languages.php:187 |
44 | msgid "French" | 44 | msgid "French" |
45 | msgstr "Français" | 45 | msgstr "Français" |
46 | 46 | ||
47 | #: application/Languages.php:185 | 47 | #: application/Languages.php:188 |
48 | msgid "Japanese" | 48 | msgid "Japanese" |
49 | msgstr "Japonais" | 49 | msgstr "Japonais" |
50 | 50 | ||
@@ -56,46 +56,46 @@ msgstr "" | |||
56 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " | 56 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " |
57 | "miniatures sont désormais désactivées. Rechargez la page." | 57 | "miniatures sont désormais désactivées. Rechargez la page." |
58 | 58 | ||
59 | #: application/Utils.php:402 | 59 | #: application/Utils.php:405 |
60 | msgid "Setting not set" | 60 | msgid "Setting not set" |
61 | msgstr "Paramètre non défini" | 61 | msgstr "Paramètre non défini" |
62 | 62 | ||
63 | #: application/Utils.php:409 | 63 | #: application/Utils.php:412 |
64 | msgid "Unlimited" | 64 | msgid "Unlimited" |
65 | msgstr "Illimité" | 65 | msgstr "Illimité" |
66 | 66 | ||
67 | #: application/Utils.php:412 | 67 | #: application/Utils.php:415 |
68 | msgid "B" | 68 | msgid "B" |
69 | msgstr "o" | 69 | msgstr "o" |
70 | 70 | ||
71 | #: application/Utils.php:412 | 71 | #: application/Utils.php:415 |
72 | msgid "kiB" | 72 | msgid "kiB" |
73 | msgstr "ko" | 73 | msgstr "ko" |
74 | 74 | ||
75 | #: application/Utils.php:412 | 75 | #: application/Utils.php:415 |
76 | msgid "MiB" | 76 | msgid "MiB" |
77 | msgstr "Mo" | 77 | msgstr "Mo" |
78 | 78 | ||
79 | #: application/Utils.php:412 | 79 | #: application/Utils.php:415 |
80 | msgid "GiB" | 80 | msgid "GiB" |
81 | msgstr "Go" | 81 | msgstr "Go" |
82 | 82 | ||
83 | #: application/bookmark/BookmarkFileService.php:183 | 83 | #: application/bookmark/BookmarkFileService.php:185 |
84 | #: application/bookmark/BookmarkFileService.php:205 | 84 | #: application/bookmark/BookmarkFileService.php:207 |
85 | #: application/bookmark/BookmarkFileService.php:227 | 85 | #: application/bookmark/BookmarkFileService.php:229 |
86 | #: application/bookmark/BookmarkFileService.php:241 | 86 | #: application/bookmark/BookmarkFileService.php:243 |
87 | msgid "You're not authorized to alter the datastore" | 87 | msgid "You're not authorized to alter the datastore" |
88 | msgstr "Vous n'êtes pas autorisé à modifier les données" | 88 | msgstr "Vous n'êtes pas autorisé à modifier les données" |
89 | 89 | ||
90 | #: application/bookmark/BookmarkFileService.php:208 | 90 | #: application/bookmark/BookmarkFileService.php:210 |
91 | msgid "This bookmarks already exists" | 91 | msgid "This bookmarks already exists" |
92 | msgstr "Ce marque-page existe déjà " | 92 | msgstr "Ce marque-page existe déjà " |
93 | 93 | ||
94 | #: application/bookmark/BookmarkInitializer.php:39 | 94 | #: application/bookmark/BookmarkInitializer.php:42 |
95 | msgid "(private bookmark with thumbnail demo)" | 95 | msgid "(private bookmark with thumbnail demo)" |
96 | msgstr "(marque page privé avec une miniature)" | 96 | msgstr "(marque page privé avec une miniature)" |
97 | 97 | ||
98 | #: application/bookmark/BookmarkInitializer.php:42 | 98 | #: application/bookmark/BookmarkInitializer.php:45 |
99 | msgid "" | 99 | msgid "" |
100 | "Shaarli will automatically pick up the thumbnail for links to a variety of " | 100 | "Shaarli will automatically pick up the thumbnail for links to a variety of " |
101 | "websites.\n" | 101 | "websites.\n" |
@@ -118,11 +118,11 @@ msgstr "" | |||
118 | "\n" | 118 | "\n" |
119 | "Maintenant, vous pouvez modifier ou supprimer les shaares créés par défaut.\n" | 119 | "Maintenant, vous pouvez modifier ou supprimer les shaares créés par défaut.\n" |
120 | 120 | ||
121 | #: application/bookmark/BookmarkInitializer.php:55 | 121 | #: application/bookmark/BookmarkInitializer.php:58 |
122 | msgid "Note: Shaare descriptions" | 122 | msgid "Note: Shaare descriptions" |
123 | msgstr "Note : Description des Shaares" | 123 | msgstr "Note : Description des Shaares" |
124 | 124 | ||
125 | #: application/bookmark/BookmarkInitializer.php:57 | 125 | #: application/bookmark/BookmarkInitializer.php:60 |
126 | msgid "" | 126 | msgid "" |
127 | "Adding a shaare without entering a URL creates a text-only \"note\" post " | 127 | "Adding a shaare without entering a URL creates a text-only \"note\" post " |
128 | "such as this one.\n" | 128 | "such as this one.\n" |
@@ -186,7 +186,7 @@ msgstr "" | |||
186 | "| Citron | Fruit | Jaune | 30 |\n" | 186 | "| Citron | Fruit | Jaune | 30 |\n" |
187 | "| Carotte | Légume | Orange | 14 |\n" | 187 | "| Carotte | Légume | Orange | 14 |\n" |
188 | 188 | ||
189 | #: application/bookmark/BookmarkInitializer.php:91 | 189 | #: application/bookmark/BookmarkInitializer.php:94 |
190 | #: application/legacy/LegacyLinkDB.php:246 | 190 | #: application/legacy/LegacyLinkDB.php:246 |
191 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | 191 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 |
192 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | 192 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 |
@@ -198,7 +198,7 @@ msgstr "" | |||
198 | "Le gestionnaire de marque-pages personnel, minimaliste, et sans base de " | 198 | "Le gestionnaire de marque-pages personnel, minimaliste, et sans base de " |
199 | "données" | 199 | "données" |
200 | 200 | ||
201 | #: application/bookmark/BookmarkInitializer.php:94 | 201 | #: application/bookmark/BookmarkInitializer.php:97 |
202 | msgid "" | 202 | msgid "" |
203 | "Welcome to Shaarli!\n" | 203 | "Welcome to Shaarli!\n" |
204 | "\n" | 204 | "\n" |
@@ -247,11 +247,11 @@ msgstr "" | |||
247 | "issues) si vous avez une suggestion ou si vous rencontrez un problème.\n" | 247 | "issues) si vous avez une suggestion ou si vous rencontrez un problème.\n" |
248 | " \n" | 248 | " \n" |
249 | 249 | ||
250 | #: application/bookmark/exception/BookmarkNotFoundException.php:13 | 250 | #: application/bookmark/exception/BookmarkNotFoundException.php:14 |
251 | msgid "The link you are trying to reach does not exist or has been deleted." | 251 | msgid "The link you are trying to reach does not exist or has been deleted." |
252 | msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé." | 252 | msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé." |
253 | 253 | ||
254 | #: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:129 | 254 | #: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:131 |
255 | msgid "" | 255 | msgid "" |
256 | "Shaarli could not create the config file. Please make sure Shaarli has the " | 256 | "Shaarli could not create the config file. Please make sure Shaarli has the " |
257 | "right to write in the folder is it installed in." | 257 | "right to write in the folder is it installed in." |
@@ -259,12 +259,12 @@ msgstr "" | |||
259 | "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que " | 259 | "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que " |
260 | "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." | 260 | "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." |
261 | 261 | ||
262 | #: application/config/ConfigManager.php:136 | 262 | #: application/config/ConfigManager.php:137 |
263 | #: application/config/ConfigManager.php:163 | 263 | #: application/config/ConfigManager.php:164 |
264 | msgid "Invalid setting key parameter. String expected, got: " | 264 | msgid "Invalid setting key parameter. String expected, got: " |
265 | msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " | 265 | msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " |
266 | 266 | ||
267 | #: application/config/exception/MissingFieldConfigException.php:21 | 267 | #: application/config/exception/MissingFieldConfigException.php:20 |
268 | #, php-format | 268 | #, php-format |
269 | msgid "Configuration value is required for %s" | 269 | msgid "Configuration value is required for %s" |
270 | msgstr "Le paramètre %s est obligatoire" | 270 | msgstr "Le paramètre %s est obligatoire" |
@@ -274,48 +274,48 @@ msgid "An error occurred while trying to save plugins loading order." | |||
274 | msgstr "" | 274 | msgstr "" |
275 | "Une erreur s'est produite lors de la sauvegarde de l'ordre des extensions." | 275 | "Une erreur s'est produite lors de la sauvegarde de l'ordre des extensions." |
276 | 276 | ||
277 | #: application/config/exception/UnauthorizedConfigException.php:16 | 277 | #: application/config/exception/UnauthorizedConfigException.php:15 |
278 | msgid "You are not authorized to alter config." | 278 | msgid "You are not authorized to alter config." |
279 | msgstr "Vous n'êtes pas autorisé à modifier la configuration." | 279 | msgstr "Vous n'êtes pas autorisé à modifier la configuration." |
280 | 280 | ||
281 | #: application/exceptions/IOException.php:22 | 281 | #: application/exceptions/IOException.php:23 |
282 | msgid "Error accessing" | 282 | msgid "Error accessing" |
283 | msgstr "Une erreur s'est produite en accédant à " | 283 | msgstr "Une erreur s'est produite en accédant à " |
284 | 284 | ||
285 | #: application/feed/FeedBuilder.php:179 | 285 | #: application/feed/FeedBuilder.php:180 |
286 | msgid "Direct link" | 286 | msgid "Direct link" |
287 | msgstr "Liens directs" | 287 | msgstr "Liens directs" |
288 | 288 | ||
289 | #: application/feed/FeedBuilder.php:181 | 289 | #: application/feed/FeedBuilder.php:182 |
290 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | 290 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 |
291 | #: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | 291 | #: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 |
292 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 | 292 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 |
293 | msgid "Permalink" | 293 | msgid "Permalink" |
294 | msgstr "Permalien" | 294 | msgstr "Permalien" |
295 | 295 | ||
296 | #: application/front/controller/admin/ConfigureController.php:54 | 296 | #: application/front/controller/admin/ConfigureController.php:56 |
297 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | 297 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 |
298 | msgid "Configure" | 298 | msgid "Configure" |
299 | msgstr "Configurer" | 299 | msgstr "Configurer" |
300 | 300 | ||
301 | #: application/front/controller/admin/ConfigureController.php:102 | 301 | #: application/front/controller/admin/ConfigureController.php:106 |
302 | #: application/legacy/LegacyUpdater.php:537 | 302 | #: application/legacy/LegacyUpdater.php:539 |
303 | msgid "You have enabled or changed thumbnails mode." | 303 | msgid "You have enabled or changed thumbnails mode." |
304 | msgstr "Vous avez activé ou changé le mode de miniatures." | 304 | msgstr "Vous avez activé ou changé le mode de miniatures." |
305 | 305 | ||
306 | #: application/front/controller/admin/ConfigureController.php:103 | 306 | #: application/front/controller/admin/ConfigureController.php:108 |
307 | #: application/front/controller/admin/ServerController.php:75 | 307 | #: application/front/controller/admin/ServerController.php:76 |
308 | #: application/legacy/LegacyUpdater.php:538 | 308 | #: application/legacy/LegacyUpdater.php:540 |
309 | msgid "Please synchronize them." | 309 | msgid "Please synchronize them." |
310 | msgstr "Merci de les synchroniser." | 310 | msgstr "Merci de les synchroniser." |
311 | 311 | ||
312 | #: application/front/controller/admin/ConfigureController.php:113 | 312 | #: application/front/controller/admin/ConfigureController.php:119 |
313 | #: application/front/controller/visitor/InstallController.php:146 | 313 | #: application/front/controller/visitor/InstallController.php:149 |
314 | msgid "Error while writing config file after configuration update." | 314 | msgid "Error while writing config file after configuration update." |
315 | msgstr "" | 315 | msgstr "" |
316 | "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." | 316 | "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." |
317 | 317 | ||
318 | #: application/front/controller/admin/ConfigureController.php:122 | 318 | #: application/front/controller/admin/ConfigureController.php:128 |
319 | msgid "Configuration was saved." | 319 | msgid "Configuration was saved." |
320 | msgstr "La configuration a été sauvegardée." | 320 | msgstr "La configuration a été sauvegardée." |
321 | 321 | ||
@@ -433,7 +433,7 @@ msgstr "Administration serveur" | |||
433 | msgid "Thumbnails cache has been cleared." | 433 | msgid "Thumbnails cache has been cleared." |
434 | msgstr "Le cache des miniatures a été vidé." | 434 | msgstr "Le cache des miniatures a été vidé." |
435 | 435 | ||
436 | #: application/front/controller/admin/ServerController.php:83 | 436 | #: application/front/controller/admin/ServerController.php:85 |
437 | msgid "Shaarli's cache folder has been cleared!" | 437 | msgid "Shaarli's cache folder has been cleared!" |
438 | msgstr "Le dossier de cache de Shaarli a été vidé !" | 438 | msgstr "Le dossier de cache de Shaarli a été vidé !" |
439 | 439 | ||
@@ -459,18 +459,18 @@ msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." | |||
459 | msgid "Invalid visibility provided." | 459 | msgid "Invalid visibility provided." |
460 | msgstr "Visibilité du lien non valide." | 460 | msgstr "Visibilité du lien non valide." |
461 | 461 | ||
462 | #: application/front/controller/admin/ShaarePublishController.php:171 | 462 | #: application/front/controller/admin/ShaarePublishController.php:173 |
463 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 | 463 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 |
464 | msgid "Edit" | 464 | msgid "Edit" |
465 | msgstr "Modifier" | 465 | msgstr "Modifier" |
466 | 466 | ||
467 | #: application/front/controller/admin/ShaarePublishController.php:174 | 467 | #: application/front/controller/admin/ShaarePublishController.php:176 |
468 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | 468 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 |
469 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 | 469 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 |
470 | msgid "Shaare" | 470 | msgid "Shaare" |
471 | msgstr "Shaare" | 471 | msgstr "Shaare" |
472 | 472 | ||
473 | #: application/front/controller/admin/ShaarePublishController.php:205 | 473 | #: application/front/controller/admin/ShaarePublishController.php:208 |
474 | msgid "Note: " | 474 | msgid "Note: " |
475 | msgstr "Note : " | 475 | msgstr "Note : " |
476 | 476 | ||
@@ -485,7 +485,7 @@ msgstr "Mise à jour des miniatures" | |||
485 | msgid "Tools" | 485 | msgid "Tools" |
486 | msgstr "Outils" | 486 | msgstr "Outils" |
487 | 487 | ||
488 | #: application/front/controller/visitor/BookmarkListController.php:120 | 488 | #: application/front/controller/visitor/BookmarkListController.php:121 |
489 | msgid "Search: " | 489 | msgid "Search: " |
490 | msgstr "Recherche : " | 490 | msgstr "Recherche : " |
491 | 491 | ||
@@ -535,12 +535,12 @@ msgstr "Une erreur inattendue s'est produite." | |||
535 | msgid "Requested page could not be found." | 535 | msgid "Requested page could not be found." |
536 | msgstr "La page demandée n'a pas pu être trouvée." | 536 | msgstr "La page demandée n'a pas pu être trouvée." |
537 | 537 | ||
538 | #: application/front/controller/visitor/InstallController.php:64 | 538 | #: application/front/controller/visitor/InstallController.php:65 |
539 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | 539 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 |
540 | msgid "Install Shaarli" | 540 | msgid "Install Shaarli" |
541 | msgstr "Installation de Shaarli" | 541 | msgstr "Installation de Shaarli" |
542 | 542 | ||
543 | #: application/front/controller/visitor/InstallController.php:83 | 543 | #: application/front/controller/visitor/InstallController.php:85 |
544 | #, php-format | 544 | #, php-format |
545 | msgid "" | 545 | msgid "" |
546 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | 546 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " |
@@ -559,14 +559,14 @@ msgstr "" | |||
559 | "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " | 559 | "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " |
560 | "adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" | 560 | "adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" |
561 | 561 | ||
562 | #: application/front/controller/visitor/InstallController.php:154 | 562 | #: application/front/controller/visitor/InstallController.php:157 |
563 | msgid "" | 563 | msgid "" |
564 | "Shaarli is now configured. Please login and start shaaring your bookmarks!" | 564 | "Shaarli is now configured. Please login and start shaaring your bookmarks!" |
565 | msgstr "" | 565 | msgstr "" |
566 | "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " | 566 | "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " |
567 | "shaare vos liens !" | 567 | "shaare vos liens !" |
568 | 568 | ||
569 | #: application/front/controller/visitor/InstallController.php:168 | 569 | #: application/front/controller/visitor/InstallController.php:171 |
570 | msgid "Insufficient permissions:" | 570 | msgid "Insufficient permissions:" |
571 | msgstr "Permissions insuffisantes :" | 571 | msgstr "Permissions insuffisantes :" |
572 | 572 | ||
@@ -580,7 +580,7 @@ msgstr "Permissions insuffisantes :" | |||
580 | msgid "Login" | 580 | msgid "Login" |
581 | msgstr "Connexion" | 581 | msgstr "Connexion" |
582 | 582 | ||
583 | #: application/front/controller/visitor/LoginController.php:77 | 583 | #: application/front/controller/visitor/LoginController.php:78 |
584 | msgid "Wrong login/password." | 584 | msgid "Wrong login/password." |
585 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." | 585 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." |
586 | 586 | ||
@@ -620,7 +620,7 @@ msgstr "" | |||
620 | msgid "Wrong token." | 620 | msgid "Wrong token." |
621 | msgstr "Jeton invalide." | 621 | msgstr "Jeton invalide." |
622 | 622 | ||
623 | #: application/helper/ApplicationUtils.php:162 | 623 | #: application/helper/ApplicationUtils.php:165 |
624 | #, php-format | 624 | #, php-format |
625 | msgid "" | 625 | msgid "" |
626 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " | 626 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " |
@@ -631,52 +631,60 @@ msgstr "" | |||
631 | "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " | 631 | "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " |
632 | "connues et devrait être mise à jour au plus tôt." | 632 | "connues et devrait être mise à jour au plus tôt." |
633 | 633 | ||
634 | #: application/helper/ApplicationUtils.php:195 | 634 | #: application/helper/ApplicationUtils.php:200 |
635 | #: application/helper/ApplicationUtils.php:215 | 635 | #: application/helper/ApplicationUtils.php:220 |
636 | msgid "directory is not readable" | 636 | msgid "directory is not readable" |
637 | msgstr "le répertoire n'est pas accessible en lecture" | 637 | msgstr "le répertoire n'est pas accessible en lecture" |
638 | 638 | ||
639 | #: application/helper/ApplicationUtils.php:218 | 639 | #: application/helper/ApplicationUtils.php:223 |
640 | msgid "directory is not writable" | 640 | msgid "directory is not writable" |
641 | msgstr "le répertoire n'est pas accessible en écriture" | 641 | msgstr "le répertoire n'est pas accessible en écriture" |
642 | 642 | ||
643 | #: application/helper/ApplicationUtils.php:240 | 643 | #: application/helper/ApplicationUtils.php:247 |
644 | msgid "file is not readable" | 644 | msgid "file is not readable" |
645 | msgstr "le fichier n'est pas accessible en lecture" | 645 | msgstr "le fichier n'est pas accessible en lecture" |
646 | 646 | ||
647 | #: application/helper/ApplicationUtils.php:243 | 647 | #: application/helper/ApplicationUtils.php:250 |
648 | msgid "file is not writable" | 648 | msgid "file is not writable" |
649 | msgstr "le fichier n'est pas accessible en écriture" | 649 | msgstr "le fichier n'est pas accessible en écriture" |
650 | 650 | ||
651 | #: application/helper/ApplicationUtils.php:277 | 651 | #: application/helper/ApplicationUtils.php:260 |
652 | msgid "" | ||
653 | "Lock can not be acquired on the datastore. You might encounter concurrent " | ||
654 | "access issues." | ||
655 | msgstr "" | ||
656 | "Le fichier datastore ne peut pas être verrouillé. Vous pourriez rencontrer " | ||
657 | "des problèmes d'accès concurrents." | ||
658 | |||
659 | #: application/helper/ApplicationUtils.php:293 | ||
652 | msgid "Configuration parsing" | 660 | msgid "Configuration parsing" |
653 | msgstr "Chargement de la configuration" | 661 | msgstr "Chargement de la configuration" |
654 | 662 | ||
655 | #: application/helper/ApplicationUtils.php:278 | 663 | #: application/helper/ApplicationUtils.php:294 |
656 | msgid "Slim Framework (routing, etc.)" | 664 | msgid "Slim Framework (routing, etc.)" |
657 | msgstr "Slim Framwork (routage, etc.)" | 665 | msgstr "Slim Framwork (routage, etc.)" |
658 | 666 | ||
659 | #: application/helper/ApplicationUtils.php:279 | 667 | #: application/helper/ApplicationUtils.php:295 |
660 | msgid "Multibyte (Unicode) string support" | 668 | msgid "Multibyte (Unicode) string support" |
661 | msgstr "Support des chaînes de caractère multibytes (Unicode)" | 669 | msgstr "Support des chaînes de caractère multibytes (Unicode)" |
662 | 670 | ||
663 | #: application/helper/ApplicationUtils.php:280 | 671 | #: application/helper/ApplicationUtils.php:296 |
664 | msgid "Required to use thumbnails" | 672 | msgid "Required to use thumbnails" |
665 | msgstr "Obligatoire pour utiliser les miniatures" | 673 | msgstr "Obligatoire pour utiliser les miniatures" |
666 | 674 | ||
667 | #: application/helper/ApplicationUtils.php:281 | 675 | #: application/helper/ApplicationUtils.php:297 |
668 | msgid "Localized text sorting (e.g. e->è->f)" | 676 | msgid "Localized text sorting (e.g. e->è->f)" |
669 | msgstr "Tri des textes traduits (ex : e->è->f)" | 677 | msgstr "Tri des textes traduits (ex : e->è->f)" |
670 | 678 | ||
671 | #: application/helper/ApplicationUtils.php:282 | 679 | #: application/helper/ApplicationUtils.php:298 |
672 | msgid "Better retrieval of bookmark metadata and thumbnail" | 680 | msgid "Better retrieval of bookmark metadata and thumbnail" |
673 | msgstr "Meilleure récupération des meta-données des marque-pages et minatures" | 681 | msgstr "Meilleure récupération des meta-données des marque-pages et minatures" |
674 | 682 | ||
675 | #: application/helper/ApplicationUtils.php:283 | 683 | #: application/helper/ApplicationUtils.php:299 |
676 | msgid "Use the translation system in gettext mode" | 684 | msgid "Use the translation system in gettext mode" |
677 | msgstr "Utiliser le système de traduction en mode gettext" | 685 | msgstr "Utiliser le système de traduction en mode gettext" |
678 | 686 | ||
679 | #: application/helper/ApplicationUtils.php:284 | 687 | #: application/helper/ApplicationUtils.php:300 |
680 | msgid "Login using LDAP server" | 688 | msgid "Login using LDAP server" |
681 | msgstr "Authentification via un serveur LDAP" | 689 | msgstr "Authentification via un serveur LDAP" |
682 | 690 | ||
@@ -750,7 +758,7 @@ msgstr "" | |||
750 | msgid "Couldn't retrieve updater class methods." | 758 | msgid "Couldn't retrieve updater class methods." |
751 | msgstr "Impossible de récupérer les méthodes de la classe Updater." | 759 | msgstr "Impossible de récupérer les méthodes de la classe Updater." |
752 | 760 | ||
753 | #: application/legacy/LegacyUpdater.php:538 | 761 | #: application/legacy/LegacyUpdater.php:540 |
754 | msgid "<a href=\"./admin/thumbnails\">" | 762 | msgid "<a href=\"./admin/thumbnails\">" |
755 | msgstr "<a href=\"./admin/thumbnails\">" | 763 | msgstr "<a href=\"./admin/thumbnails\">" |
756 | 764 | ||
@@ -776,11 +784,11 @@ msgstr "" | |||
776 | "a été importé avec succès en %d secondes : %d liens importés, %d liens " | 784 | "a été importé avec succès en %d secondes : %d liens importés, %d liens " |
777 | "écrasés, %d liens ignorés." | 785 | "écrasés, %d liens ignorés." |
778 | 786 | ||
779 | #: application/plugin/PluginManager.php:124 | 787 | #: application/plugin/PluginManager.php:125 |
780 | msgid " [plugin incompatibility]: " | 788 | msgid " [plugin incompatibility]: " |
781 | msgstr " [incompatibilité de l'extension] : " | 789 | msgstr " [incompatibilité de l'extension] : " |
782 | 790 | ||
783 | #: application/plugin/exception/PluginFileNotFoundException.php:21 | 791 | #: application/plugin/exception/PluginFileNotFoundException.php:22 |
784 | #, php-format | 792 | #, php-format |
785 | msgid "Plugin \"%s\" files not found." | 793 | msgid "Plugin \"%s\" files not found." |
786 | msgstr "Les fichiers de l'extension \"%s\" sont introuvables." | 794 | msgstr "Les fichiers de l'extension \"%s\" sont introuvables." |
@@ -794,7 +802,7 @@ msgstr "Impossible de purger %s : le répertoire n'existe pas" | |||
794 | msgid "An error occurred while running the update " | 802 | msgid "An error occurred while running the update " |
795 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " | 803 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " |
796 | 804 | ||
797 | #: index.php:80 | 805 | #: index.php:81 |
798 | msgid "Shared bookmarks on " | 806 | msgid "Shared bookmarks on " |
799 | msgstr "Liens partagés sur " | 807 | msgstr "Liens partagés sur " |
800 | 808 | ||
@@ -811,11 +819,11 @@ msgstr "Shaare" | |||
811 | msgid "Adds the addlink input on the linklist page." | 819 | msgid "Adds the addlink input on the linklist page." |
812 | msgstr "Ajoute le formulaire d'ajout de liens sur la page principale." | 820 | msgstr "Ajoute le formulaire d'ajout de liens sur la page principale." |
813 | 821 | ||
814 | #: plugins/archiveorg/archiveorg.php:28 | 822 | #: plugins/archiveorg/archiveorg.php:29 |
815 | msgid "View on archive.org" | 823 | msgid "View on archive.org" |
816 | msgstr "Voir sur archive.org" | 824 | msgstr "Voir sur archive.org" |
817 | 825 | ||
818 | #: plugins/archiveorg/archiveorg.php:41 | 826 | #: plugins/archiveorg/archiveorg.php:42 |
819 | msgid "For each link, add an Archive.org icon." | 827 | msgid "For each link, add an Archive.org icon." |
820 | msgstr "Pour chaque lien, ajoute une icône pour Archive.org." | 828 | msgstr "Pour chaque lien, ajoute une icône pour Archive.org." |
821 | 829 | ||
@@ -845,7 +853,7 @@ msgstr "Couleur de fond (gris léger)" | |||
845 | msgid "Dark main color (e.g. visited links)" | 853 | msgid "Dark main color (e.g. visited links)" |
846 | msgstr "Couleur principale sombre (ex : les liens visités)" | 854 | msgstr "Couleur principale sombre (ex : les liens visités)" |
847 | 855 | ||
848 | #: plugins/demo_plugin/demo_plugin.php:477 | 856 | #: plugins/demo_plugin/demo_plugin.php:478 |
849 | msgid "" | 857 | msgid "" |
850 | "A demo plugin covering all use cases for template designers and plugin " | 858 | "A demo plugin covering all use cases for template designers and plugin " |
851 | "developers." | 859 | "developers." |
@@ -853,11 +861,11 @@ msgstr "" | |||
853 | "Une extension de démonstration couvrant tous les cas d'utilisation pour les " | 861 | "Une extension de démonstration couvrant tous les cas d'utilisation pour les " |
854 | "designers de thèmes et les développeurs d'extensions." | 862 | "designers de thèmes et les développeurs d'extensions." |
855 | 863 | ||
856 | #: plugins/demo_plugin/demo_plugin.php:478 | 864 | #: plugins/demo_plugin/demo_plugin.php:479 |
857 | msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed." | 865 | msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed." |
858 | msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé." | 866 | msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé." |
859 | 867 | ||
860 | #: plugins/demo_plugin/demo_plugin.php:479 | 868 | #: plugins/demo_plugin/demo_plugin.php:480 |
861 | msgid "Other demo parameter" | 869 | msgid "Other demo parameter" |
862 | msgstr "Un autre paramètre de démo" | 870 | msgstr "Un autre paramètre de démo" |
863 | 871 | ||
@@ -879,7 +887,7 @@ msgstr "" | |||
879 | msgid "Isso server URL (without 'http://')" | 887 | msgid "Isso server URL (without 'http://')" |
880 | msgstr "URL du serveur Isso (sans 'http://')" | 888 | msgstr "URL du serveur Isso (sans 'http://')" |
881 | 889 | ||
882 | #: plugins/piwik/piwik.php:23 | 890 | #: plugins/piwik/piwik.php:24 |
883 | msgid "" | 891 | msgid "" |
884 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " | 892 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " |
885 | "administration page." | 893 | "administration page." |
@@ -887,27 +895,27 @@ msgstr "" | |||
887 | "Erreur de l'extension Piwik : Merci de définir les paramètres PIWIK_URL et " | 895 | "Erreur de l'extension Piwik : Merci de définir les paramètres PIWIK_URL et " |
888 | "PIWIK_SITEID dans la page d'administration des extensions." | 896 | "PIWIK_SITEID dans la page d'administration des extensions." |
889 | 897 | ||
890 | #: plugins/piwik/piwik.php:72 | 898 | #: plugins/piwik/piwik.php:73 |
891 | msgid "A plugin that adds Piwik tracking code to Shaarli pages." | 899 | msgid "A plugin that adds Piwik tracking code to Shaarli pages." |
892 | msgstr "Ajoute le code de traçage de Piwik sur les pages de Shaarli." | 900 | msgstr "Ajoute le code de traçage de Piwik sur les pages de Shaarli." |
893 | 901 | ||
894 | #: plugins/piwik/piwik.php:73 | 902 | #: plugins/piwik/piwik.php:74 |
895 | msgid "Piwik URL" | 903 | msgid "Piwik URL" |
896 | msgstr "URL de Piwik" | 904 | msgstr "URL de Piwik" |
897 | 905 | ||
898 | #: plugins/piwik/piwik.php:74 | 906 | #: plugins/piwik/piwik.php:75 |
899 | msgid "Piwik site ID" | 907 | msgid "Piwik site ID" |
900 | msgstr "Site ID de Piwik" | 908 | msgstr "Site ID de Piwik" |
901 | 909 | ||
902 | #: plugins/playvideos/playvideos.php:25 | 910 | #: plugins/playvideos/playvideos.php:26 |
903 | msgid "Video player" | 911 | msgid "Video player" |
904 | msgstr "Lecteur vidéo" | 912 | msgstr "Lecteur vidéo" |
905 | 913 | ||
906 | #: plugins/playvideos/playvideos.php:28 | 914 | #: plugins/playvideos/playvideos.php:29 |
907 | msgid "Play Videos" | 915 | msgid "Play Videos" |
908 | msgstr "Jouer les vidéos" | 916 | msgstr "Jouer les vidéos" |
909 | 917 | ||
910 | #: plugins/playvideos/playvideos.php:59 | 918 | #: plugins/playvideos/playvideos.php:60 |
911 | msgid "Add a button in the toolbar allowing to watch all videos." | 919 | msgid "Add a button in the toolbar allowing to watch all videos." |
912 | msgstr "" | 920 | msgstr "" |
913 | "Ajoute un bouton dans la barre de menu pour regarder toutes les vidéos." | 921 | "Ajoute un bouton dans la barre de menu pour regarder toutes les vidéos." |
@@ -935,11 +943,11 @@ msgstr "Mauvaise réponse du hub %s" | |||
935 | msgid "Enable PubSubHubbub feed publishing." | 943 | msgid "Enable PubSubHubbub feed publishing." |
936 | msgstr "Active la publication de flux vers PubSubHubbub." | 944 | msgstr "Active la publication de flux vers PubSubHubbub." |
937 | 945 | ||
938 | #: plugins/qrcode/qrcode.php:73 plugins/wallabag/wallabag.php:71 | 946 | #: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72 |
939 | msgid "For each link, add a QRCode icon." | 947 | msgid "For each link, add a QRCode icon." |
940 | msgstr "Pour chaque lien, ajouter une icône de QRCode." | 948 | msgstr "Pour chaque lien, ajouter une icône de QRCode." |
941 | 949 | ||
942 | #: plugins/wallabag/wallabag.php:21 | 950 | #: plugins/wallabag/wallabag.php:22 |
943 | msgid "" | 951 | msgid "" |
944 | "Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the " | 952 | "Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the " |
945 | "plugin administration page." | 953 | "plugin administration page." |
@@ -947,15 +955,15 @@ msgstr "" | |||
947 | "Erreur de l'extension Wallabag : Merci de définir le paramètre « " | 955 | "Erreur de l'extension Wallabag : Merci de définir le paramètre « " |
948 | "WALLABAG_URL » dans la page d'administration des extensions." | 956 | "WALLABAG_URL » dans la page d'administration des extensions." |
949 | 957 | ||
950 | #: plugins/wallabag/wallabag.php:48 | 958 | #: plugins/wallabag/wallabag.php:49 |
951 | msgid "Save to wallabag" | 959 | msgid "Save to wallabag" |
952 | msgstr "Sauvegarder dans Wallabag" | 960 | msgstr "Sauvegarder dans Wallabag" |
953 | 961 | ||
954 | #: plugins/wallabag/wallabag.php:72 | 962 | #: plugins/wallabag/wallabag.php:73 |
955 | msgid "Wallabag API URL" | 963 | msgid "Wallabag API URL" |
956 | msgstr "URL de l'API Wallabag" | 964 | msgstr "URL de l'API Wallabag" |
957 | 965 | ||
958 | #: plugins/wallabag/wallabag.php:73 | 966 | #: plugins/wallabag/wallabag.php:74 |
959 | msgid "Wallabag API version (1 or 2)" | 967 | msgid "Wallabag API version (1 or 2)" |
960 | msgstr "Version de l'API Wallabag (1 ou 2)" | 968 | msgstr "Version de l'API Wallabag (1 ou 2)" |
961 | 969 | ||
@@ -31,6 +31,7 @@ use Psr\Log\LogLevel; | |||
31 | use Shaarli\Config\ConfigManager; | 31 | use Shaarli\Config\ConfigManager; |
32 | use Shaarli\Container\ContainerBuilder; | 32 | use Shaarli\Container\ContainerBuilder; |
33 | use Shaarli\Languages; | 33 | use Shaarli\Languages; |
34 | use Shaarli\Plugin\PluginManager; | ||
34 | use Shaarli\Security\BanManager; | 35 | use Shaarli\Security\BanManager; |
35 | use Shaarli\Security\CookieManager; | 36 | use Shaarli\Security\CookieManager; |
36 | use Shaarli\Security\LoginManager; | 37 | use Shaarli\Security\LoginManager; |
@@ -87,7 +88,17 @@ date_default_timezone_set($conf->get('general.timezone', 'UTC')); | |||
87 | 88 | ||
88 | $loginManager->checkLoginState(client_ip_id($_SERVER)); | 89 | $loginManager->checkLoginState(client_ip_id($_SERVER)); |
89 | 90 | ||
90 | $containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager, $logger); | 91 | $pluginManager = new PluginManager($conf); |
92 | $pluginManager->load($conf->get('general.enabled_plugins', [])); | ||
93 | |||
94 | $containerBuilder = new ContainerBuilder( | ||
95 | $conf, | ||
96 | $sessionManager, | ||
97 | $cookieManager, | ||
98 | $loginManager, | ||
99 | $pluginManager, | ||
100 | $logger | ||
101 | ); | ||
91 | $container = $containerBuilder->build(); | 102 | $container = $containerBuilder->build(); |
92 | $app = new App($container); | 103 | $app = new App($container); |
93 | 104 | ||
@@ -154,6 +165,15 @@ $app->group('/admin', function () { | |||
154 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); | 165 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); |
155 | })->add('\Shaarli\Front\ShaarliAdminMiddleware'); | 166 | })->add('\Shaarli\Front\ShaarliAdminMiddleware'); |
156 | 167 | ||
168 | $app->group('/plugin', function () use ($pluginManager) { | ||
169 | foreach ($pluginManager->getRegisteredRoutes() as $pluginName => $routes) { | ||
170 | $this->group('/' . $pluginName, function () use ($routes) { | ||
171 | foreach ($routes as $route) { | ||
172 | $this->{strtolower($route['method'])}('/' . ltrim($route['route'], '/'), $route['callable']); | ||
173 | } | ||
174 | }); | ||
175 | } | ||
176 | })->add('\Shaarli\Front\ShaarliMiddleware'); | ||
157 | 177 | ||
158 | // REST API routes | 178 | // REST API routes |
159 | $app->group('/api/v1', function () { | 179 | $app->group('/api/v1', function () { |
@@ -18,5 +18,6 @@ | |||
18 | <rule ref="PSR1.Files.SideEffects.FoundWithSymbols"> | 18 | <rule ref="PSR1.Files.SideEffects.FoundWithSymbols"> |
19 | <!-- index.php bootstraps everything, so yes mixed symbols with side effects --> | 19 | <!-- index.php bootstraps everything, so yes mixed symbols with side effects --> |
20 | <exclude-pattern>index.php</exclude-pattern> | 20 | <exclude-pattern>index.php</exclude-pattern> |
21 | <exclude-pattern>plugins/*</exclude-pattern> | ||
21 | </rule> | 22 | </rule> |
22 | </ruleset> | 23 | </ruleset> |
diff --git a/plugins/demo_plugin/DemoPluginController.php b/plugins/demo_plugin/DemoPluginController.php new file mode 100644 index 00000000..b8ace9c8 --- /dev/null +++ b/plugins/demo_plugin/DemoPluginController.php | |||
@@ -0,0 +1,24 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\DemoPlugin; | ||
6 | |||
7 | use Shaarli\Front\Controller\Admin\ShaarliAdminController; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | class DemoPluginController extends ShaarliAdminController | ||
12 | { | ||
13 | public function index(Request $request, Response $response): Response | ||
14 | { | ||
15 | $this->assignView( | ||
16 | 'content', | ||
17 | '<div class="center">' . | ||
18 | 'This is a demo page. I have access to Shaarli container, so I\'m free to do whatever I want here.' . | ||
19 | '</div>' | ||
20 | ); | ||
21 | |||
22 | return $response->write($this->render('pluginscontent')); | ||
23 | } | ||
24 | } | ||
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php index 22d27b68..15cfc2c5 100644 --- a/plugins/demo_plugin/demo_plugin.php +++ b/plugins/demo_plugin/demo_plugin.php | |||
@@ -7,6 +7,8 @@ | |||
7 | * Can be used by plugin developers to make their own plugin. | 7 | * Can be used by plugin developers to make their own plugin. |
8 | */ | 8 | */ |
9 | 9 | ||
10 | require_once __DIR__ . '/DemoPluginController.php'; | ||
11 | |||
10 | /* | 12 | /* |
11 | * RENDER HEADER, INCLUDES, FOOTER | 13 | * RENDER HEADER, INCLUDES, FOOTER |
12 | * | 14 | * |
@@ -60,6 +62,17 @@ function demo_plugin_init($conf) | |||
60 | return $errors; | 62 | return $errors; |
61 | } | 63 | } |
62 | 64 | ||
65 | function demo_plugin_register_routes(): array | ||
66 | { | ||
67 | return [ | ||
68 | [ | ||
69 | 'method' => 'GET', | ||
70 | 'route' => '/custom', | ||
71 | 'callable' => 'Shaarli\DemoPlugin\DemoPluginController:index', | ||
72 | ], | ||
73 | ]; | ||
74 | } | ||
75 | |||
63 | /** | 76 | /** |
64 | * Hook render_header. | 77 | * Hook render_header. |
65 | * Executed on every page render. | 78 | * Executed on every page render. |
@@ -304,7 +317,11 @@ function hook_demo_plugin_render_editlink($data) | |||
304 | function hook_demo_plugin_render_tools($data) | 317 | function hook_demo_plugin_render_tools($data) |
305 | { | 318 | { |
306 | // field_plugin | 319 | // field_plugin |
307 | $data['tools_plugin'][] = 'tools_plugin'; | 320 | $data['tools_plugin'][] = '<div class="tools-item"> |
321 | <a href="' . $data['_BASE_PATH_'] . '/plugin/demo_plugin/custom"> | ||
322 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Demo Plugin Custom Route</span> | ||
323 | </a> | ||
324 | </div>'; | ||
308 | 325 | ||
309 | return $data; | 326 | return $data; |
310 | } | 327 | } |
diff --git a/tests/PluginManagerTest.php b/tests/PluginManagerTest.php index efef5e87..8947f679 100644 --- a/tests/PluginManagerTest.php +++ b/tests/PluginManagerTest.php | |||
@@ -120,4 +120,43 @@ class PluginManagerTest extends \Shaarli\TestCase | |||
120 | $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); | 120 | $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); |
121 | $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); | 121 | $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); |
122 | } | 122 | } |
123 | |||
124 | /** | ||
125 | * Test plugin custom routes - note that there is no check on callable functions | ||
126 | */ | ||
127 | public function testRegisteredRoutes(): void | ||
128 | { | ||
129 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | ||
130 | $this->pluginManager->load([self::$pluginName]); | ||
131 | |||
132 | $expectedParameters = [ | ||
133 | [ | ||
134 | 'method' => 'GET', | ||
135 | 'route' => '/test', | ||
136 | 'callable' => 'getFunction', | ||
137 | ], | ||
138 | [ | ||
139 | 'method' => 'POST', | ||
140 | 'route' => '/custom', | ||
141 | 'callable' => 'postFunction', | ||
142 | ], | ||
143 | ]; | ||
144 | $meta = $this->pluginManager->getRegisteredRoutes(); | ||
145 | static::assertSame($expectedParameters, $meta[self::$pluginName]); | ||
146 | } | ||
147 | |||
148 | /** | ||
149 | * Test plugin custom routes with invalid route | ||
150 | */ | ||
151 | public function testRegisteredRoutesInvalid(): void | ||
152 | { | ||
153 | $plugin = 'test_route_invalid'; | ||
154 | $this->pluginManager->load([$plugin]); | ||
155 | |||
156 | $meta = $this->pluginManager->getRegisteredRoutes(); | ||
157 | static::assertSame([], $meta); | ||
158 | |||
159 | $errors = $this->pluginManager->getErrors(); | ||
160 | static::assertSame(['test_route_invalid [plugin incompatibility]: trying to register invalid route.'], $errors); | ||
161 | } | ||
123 | } | 162 | } |
diff --git a/tests/bookmark/LinkUtilsTest.php b/tests/bookmark/LinkUtilsTest.php index ddab4e3c..46a7f1fe 100644 --- a/tests/bookmark/LinkUtilsTest.php +++ b/tests/bookmark/LinkUtilsTest.php | |||
@@ -245,6 +245,16 @@ class LinkUtilsTest extends TestCase | |||
245 | $this->assertFalse(html_extract_tag('description', $html)); | 245 | $this->assertFalse(html_extract_tag('description', $html)); |
246 | } | 246 | } |
247 | 247 | ||
248 | public function testHtmlExtractDescriptionFromGoogleRealCase(): void | ||
249 | { | ||
250 | $html = 'id="gsr"><meta content="Fêtes de fin d\'année" property="twitter:title"><meta '. | ||
251 | 'content="Bonnes fêtes de fin d\'année ! #GoogleDoodle" property="twitter:description">'. | ||
252 | '<meta content="Bonnes fêtes de fin d\'année ! #GoogleDoodle" property="og:description">'. | ||
253 | '<meta content="summary_large_image" property="twitter:card"><meta co' | ||
254 | ; | ||
255 | $this->assertSame('Bonnes fêtes de fin d\'année ! #GoogleDoodle', html_extract_tag('description', $html)); | ||
256 | } | ||
257 | |||
248 | /** | 258 | /** |
249 | * Test the header callback with valid value | 259 | * Test the header callback with valid value |
250 | */ | 260 | */ |
diff --git a/tests/container/ContainerBuilderTest.php b/tests/container/ContainerBuilderTest.php index 3d43c344..04d4ef01 100644 --- a/tests/container/ContainerBuilderTest.php +++ b/tests/container/ContainerBuilderTest.php | |||
@@ -43,11 +43,15 @@ class ContainerBuilderTest extends TestCase | |||
43 | /** @var CookieManager */ | 43 | /** @var CookieManager */ |
44 | protected $cookieManager; | 44 | protected $cookieManager; |
45 | 45 | ||
46 | /** @var PluginManager */ | ||
47 | protected $pluginManager; | ||
48 | |||
46 | public function setUp(): void | 49 | public function setUp(): void |
47 | { | 50 | { |
48 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 51 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
49 | $this->sessionManager = $this->createMock(SessionManager::class); | 52 | $this->sessionManager = $this->createMock(SessionManager::class); |
50 | $this->cookieManager = $this->createMock(CookieManager::class); | 53 | $this->cookieManager = $this->createMock(CookieManager::class); |
54 | $this->pluginManager = $this->createMock(PluginManager::class); | ||
51 | 55 | ||
52 | $this->loginManager = $this->createMock(LoginManager::class); | 56 | $this->loginManager = $this->createMock(LoginManager::class); |
53 | $this->loginManager->method('isLoggedIn')->willReturn(true); | 57 | $this->loginManager->method('isLoggedIn')->willReturn(true); |
@@ -57,6 +61,7 @@ class ContainerBuilderTest extends TestCase | |||
57 | $this->sessionManager, | 61 | $this->sessionManager, |
58 | $this->cookieManager, | 62 | $this->cookieManager, |
59 | $this->loginManager, | 63 | $this->loginManager, |
64 | $this->pluginManager, | ||
60 | $this->createMock(LoggerInterface::class) | 65 | $this->createMock(LoggerInterface::class) |
61 | ); | 66 | ); |
62 | } | 67 | } |
diff --git a/tests/feed/CachedPageTest.php b/tests/feed/CachedPageTest.php index 904db9dc..1decfaf3 100644 --- a/tests/feed/CachedPageTest.php +++ b/tests/feed/CachedPageTest.php | |||
@@ -40,10 +40,10 @@ class CachedPageTest extends \Shaarli\TestCase | |||
40 | */ | 40 | */ |
41 | public function testConstruct() | 41 | public function testConstruct() |
42 | { | 42 | { |
43 | new CachedPage(self::$testCacheDir, '', true); | 43 | new CachedPage(self::$testCacheDir, '', true, null); |
44 | new CachedPage(self::$testCacheDir, '', false); | 44 | new CachedPage(self::$testCacheDir, '', false, null); |
45 | new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true); | 45 | new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true, null); |
46 | new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false); | 46 | new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false, null); |
47 | $this->addToAssertionCount(1); | 47 | $this->addToAssertionCount(1); |
48 | } | 48 | } |
49 | 49 | ||
@@ -52,7 +52,7 @@ class CachedPageTest extends \Shaarli\TestCase | |||
52 | */ | 52 | */ |
53 | public function testCache() | 53 | public function testCache() |
54 | { | 54 | { |
55 | $page = new CachedPage(self::$testCacheDir, self::$url, true); | 55 | $page = new CachedPage(self::$testCacheDir, self::$url, true, null); |
56 | 56 | ||
57 | $this->assertFileNotExists(self::$filename); | 57 | $this->assertFileNotExists(self::$filename); |
58 | $page->cache('<p>Some content</p>'); | 58 | $page->cache('<p>Some content</p>'); |
@@ -68,7 +68,7 @@ class CachedPageTest extends \Shaarli\TestCase | |||
68 | */ | 68 | */ |
69 | public function testShouldNotCache() | 69 | public function testShouldNotCache() |
70 | { | 70 | { |
71 | $page = new CachedPage(self::$testCacheDir, self::$url, false); | 71 | $page = new CachedPage(self::$testCacheDir, self::$url, false, null); |
72 | 72 | ||
73 | $this->assertFileNotExists(self::$filename); | 73 | $this->assertFileNotExists(self::$filename); |
74 | $page->cache('<p>Some content</p>'); | 74 | $page->cache('<p>Some content</p>'); |
@@ -80,7 +80,7 @@ class CachedPageTest extends \Shaarli\TestCase | |||
80 | */ | 80 | */ |
81 | public function testCachedVersion() | 81 | public function testCachedVersion() |
82 | { | 82 | { |
83 | $page = new CachedPage(self::$testCacheDir, self::$url, true); | 83 | $page = new CachedPage(self::$testCacheDir, self::$url, true, null); |
84 | 84 | ||
85 | $this->assertFileNotExists(self::$filename); | 85 | $this->assertFileNotExists(self::$filename); |
86 | $page->cache('<p>Some content</p>'); | 86 | $page->cache('<p>Some content</p>'); |
@@ -96,7 +96,7 @@ class CachedPageTest extends \Shaarli\TestCase | |||
96 | */ | 96 | */ |
97 | public function testCachedVersionNoFile() | 97 | public function testCachedVersionNoFile() |
98 | { | 98 | { |
99 | $page = new CachedPage(self::$testCacheDir, self::$url, true); | 99 | $page = new CachedPage(self::$testCacheDir, self::$url, true, null); |
100 | 100 | ||
101 | $this->assertFileNotExists(self::$filename); | 101 | $this->assertFileNotExists(self::$filename); |
102 | $this->assertEquals( | 102 | $this->assertEquals( |
@@ -110,7 +110,7 @@ class CachedPageTest extends \Shaarli\TestCase | |||
110 | */ | 110 | */ |
111 | public function testNoCachedVersion() | 111 | public function testNoCachedVersion() |
112 | { | 112 | { |
113 | $page = new CachedPage(self::$testCacheDir, self::$url, false); | 113 | $page = new CachedPage(self::$testCacheDir, self::$url, false, null); |
114 | 114 | ||
115 | $this->assertFileNotExists(self::$filename); | 115 | $this->assertFileNotExists(self::$filename); |
116 | $this->assertEquals( | 116 | $this->assertEquals( |
@@ -118,4 +118,43 @@ class CachedPageTest extends \Shaarli\TestCase | |||
118 | $page->cachedVersion() | 118 | $page->cachedVersion() |
119 | ); | 119 | ); |
120 | } | 120 | } |
121 | |||
122 | /** | ||
123 | * Return a page's cached content within date period | ||
124 | */ | ||
125 | public function testCachedVersionInDatePeriod() | ||
126 | { | ||
127 | $period = new \DatePeriod( | ||
128 | new \DateTime('yesterday'), | ||
129 | new \DateInterval('P1D'), | ||
130 | new \DateTime('tomorrow') | ||
131 | ); | ||
132 | $page = new CachedPage(self::$testCacheDir, self::$url, true, $period); | ||
133 | |||
134 | $this->assertFileNotExists(self::$filename); | ||
135 | $page->cache('<p>Some content</p>'); | ||
136 | $this->assertFileExists(self::$filename); | ||
137 | $this->assertEquals( | ||
138 | '<p>Some content</p>', | ||
139 | $page->cachedVersion() | ||
140 | ); | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Return a page's cached content outside of date period | ||
145 | */ | ||
146 | public function testCachedVersionNotInDatePeriod() | ||
147 | { | ||
148 | $period = new \DatePeriod( | ||
149 | new \DateTime('yesterday noon'), | ||
150 | new \DateInterval('P1D'), | ||
151 | new \DateTime('yesterday midnight') | ||
152 | ); | ||
153 | $page = new CachedPage(self::$testCacheDir, self::$url, true, $period); | ||
154 | |||
155 | $this->assertFileNotExists(self::$filename); | ||
156 | $page->cache('<p>Some content</p>'); | ||
157 | $this->assertFileExists(self::$filename); | ||
158 | $this->assertNull($page->cachedVersion()); | ||
159 | } | ||
121 | } | 160 | } |
diff --git a/tests/helper/DailyPageHelperTest.php b/tests/helper/DailyPageHelperTest.php index 5255b7b1..2d745800 100644 --- a/tests/helper/DailyPageHelperTest.php +++ b/tests/helper/DailyPageHelperTest.php | |||
@@ -4,6 +4,8 @@ declare(strict_types=1); | |||
4 | 4 | ||
5 | namespace Shaarli\Helper; | 5 | namespace Shaarli\Helper; |
6 | 6 | ||
7 | use DateTimeImmutable; | ||
8 | use DateTimeInterface; | ||
7 | use Shaarli\Bookmark\Bookmark; | 9 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\TestCase; | 10 | use Shaarli\TestCase; |
9 | use Slim\Http\Request; | 11 | use Slim\Http\Request; |
@@ -32,7 +34,7 @@ class DailyPageHelperTest extends TestCase | |||
32 | string $type, | 34 | string $type, |
33 | string $input, | 35 | string $input, |
34 | ?Bookmark $bookmark, | 36 | ?Bookmark $bookmark, |
35 | \DateTimeInterface $expectedDateTime, | 37 | DateTimeInterface $expectedDateTime, |
36 | string $compareFormat = 'Ymd' | 38 | string $compareFormat = 'Ymd' |
37 | ): void { | 39 | ): void { |
38 | $dateTime = DailyPageHelper::extractRequestedDateTime($type, $input, $bookmark); | 40 | $dateTime = DailyPageHelper::extractRequestedDateTime($type, $input, $bookmark); |
@@ -71,8 +73,8 @@ class DailyPageHelperTest extends TestCase | |||
71 | */ | 73 | */ |
72 | public function testGetStartDatesByType( | 74 | public function testGetStartDatesByType( |
73 | string $type, | 75 | string $type, |
74 | \DateTimeImmutable $dateTime, | 76 | DateTimeImmutable $dateTime, |
75 | \DateTimeInterface $expectedDateTime | 77 | DateTimeInterface $expectedDateTime |
76 | ): void { | 78 | ): void { |
77 | $startDateTime = DailyPageHelper::getStartDateTimeByType($type, $dateTime); | 79 | $startDateTime = DailyPageHelper::getStartDateTimeByType($type, $dateTime); |
78 | 80 | ||
@@ -84,7 +86,7 @@ class DailyPageHelperTest extends TestCase | |||
84 | $this->expectException(\Exception::class); | 86 | $this->expectException(\Exception::class); |
85 | $this->expectExceptionMessage('Unsupported daily format type'); | 87 | $this->expectExceptionMessage('Unsupported daily format type'); |
86 | 88 | ||
87 | DailyPageHelper::getStartDateTimeByType('nope', new \DateTimeImmutable()); | 89 | DailyPageHelper::getStartDateTimeByType('nope', new DateTimeImmutable()); |
88 | } | 90 | } |
89 | 91 | ||
90 | /** | 92 | /** |
@@ -92,8 +94,8 @@ class DailyPageHelperTest extends TestCase | |||
92 | */ | 94 | */ |
93 | public function testGetEndDatesByType( | 95 | public function testGetEndDatesByType( |
94 | string $type, | 96 | string $type, |
95 | \DateTimeImmutable $dateTime, | 97 | DateTimeImmutable $dateTime, |
96 | \DateTimeInterface $expectedDateTime | 98 | DateTimeInterface $expectedDateTime |
97 | ): void { | 99 | ): void { |
98 | $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dateTime); | 100 | $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dateTime); |
99 | 101 | ||
@@ -105,7 +107,7 @@ class DailyPageHelperTest extends TestCase | |||
105 | $this->expectException(\Exception::class); | 107 | $this->expectException(\Exception::class); |
106 | $this->expectExceptionMessage('Unsupported daily format type'); | 108 | $this->expectExceptionMessage('Unsupported daily format type'); |
107 | 109 | ||
108 | DailyPageHelper::getEndDateTimeByType('nope', new \DateTimeImmutable()); | 110 | DailyPageHelper::getEndDateTimeByType('nope', new DateTimeImmutable()); |
109 | } | 111 | } |
110 | 112 | ||
111 | /** | 113 | /** |
@@ -113,7 +115,7 @@ class DailyPageHelperTest extends TestCase | |||
113 | */ | 115 | */ |
114 | public function testGeDescriptionsByType( | 116 | public function testGeDescriptionsByType( |
115 | string $type, | 117 | string $type, |
116 | \DateTimeImmutable $dateTime, | 118 | DateTimeImmutable $dateTime, |
117 | string $expectedDescription | 119 | string $expectedDescription |
118 | ): void { | 120 | ): void { |
119 | $description = DailyPageHelper::getDescriptionByType($type, $dateTime); | 121 | $description = DailyPageHelper::getDescriptionByType($type, $dateTime); |
@@ -121,12 +123,25 @@ class DailyPageHelperTest extends TestCase | |||
121 | static::assertEquals($expectedDescription, $description); | 123 | static::assertEquals($expectedDescription, $description); |
122 | } | 124 | } |
123 | 125 | ||
126 | /** | ||
127 | * @dataProvider getDescriptionsByTypeNotIncludeRelative | ||
128 | */ | ||
129 | public function testGeDescriptionsByTypeNotIncludeRelative( | ||
130 | string $type, | ||
131 | \DateTimeImmutable $dateTime, | ||
132 | string $expectedDescription | ||
133 | ): void { | ||
134 | $description = DailyPageHelper::getDescriptionByType($type, $dateTime, false); | ||
135 | |||
136 | static::assertEquals($expectedDescription, $description); | ||
137 | } | ||
138 | |||
124 | public function getDescriptionByTypeExceptionUnknownType(): void | 139 | public function getDescriptionByTypeExceptionUnknownType(): void |
125 | { | 140 | { |
126 | $this->expectException(\Exception::class); | 141 | $this->expectException(\Exception::class); |
127 | $this->expectExceptionMessage('Unsupported daily format type'); | 142 | $this->expectExceptionMessage('Unsupported daily format type'); |
128 | 143 | ||
129 | DailyPageHelper::getDescriptionByType('nope', new \DateTimeImmutable()); | 144 | DailyPageHelper::getDescriptionByType('nope', new DateTimeImmutable()); |
130 | } | 145 | } |
131 | 146 | ||
132 | /** | 147 | /** |
@@ -147,6 +162,29 @@ class DailyPageHelperTest extends TestCase | |||
147 | } | 162 | } |
148 | 163 | ||
149 | /** | 164 | /** |
165 | * @dataProvider getCacheDatePeriodByType | ||
166 | */ | ||
167 | public function testGetCacheDatePeriodByType( | ||
168 | string $type, | ||
169 | DateTimeImmutable $requested, | ||
170 | DateTimeInterface $start, | ||
171 | DateTimeInterface $end | ||
172 | ): void { | ||
173 | $period = DailyPageHelper::getCacheDatePeriodByType($type, $requested); | ||
174 | |||
175 | static::assertEquals($start, $period->getStartDate()); | ||
176 | static::assertEquals($end, $period->getEndDate()); | ||
177 | } | ||
178 | |||
179 | public function testGetCacheDatePeriodByTypeExceptionUnknownType(): void | ||
180 | { | ||
181 | $this->expectException(\Exception::class); | ||
182 | $this->expectExceptionMessage('Unsupported daily format type'); | ||
183 | |||
184 | DailyPageHelper::getCacheDatePeriodByType('nope'); | ||
185 | } | ||
186 | |||
187 | /** | ||
150 | * Data provider for testExtractRequestedType() test method. | 188 | * Data provider for testExtractRequestedType() test method. |
151 | */ | 189 | */ |
152 | public function getRequestedTypes(): array | 190 | public function getRequestedTypes(): array |
@@ -216,9 +254,9 @@ class DailyPageHelperTest extends TestCase | |||
216 | public function getStartDatesByType(): array | 254 | public function getStartDatesByType(): array |
217 | { | 255 | { |
218 | return [ | 256 | return [ |
219 | [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')], | 257 | [DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')], |
220 | [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')], | 258 | [DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')], |
221 | [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')], | 259 | [DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')], |
222 | ]; | 260 | ]; |
223 | } | 261 | } |
224 | 262 | ||
@@ -228,9 +266,9 @@ class DailyPageHelperTest extends TestCase | |||
228 | public function getEndDatesByType(): array | 266 | public function getEndDatesByType(): array |
229 | { | 267 | { |
230 | return [ | 268 | return [ |
231 | [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')], | 269 | [DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')], |
232 | [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')], | 270 | [DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')], |
233 | [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')], | 271 | [DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')], |
234 | ]; | 272 | ]; |
235 | } | 273 | } |
236 | 274 | ||
@@ -240,8 +278,22 @@ class DailyPageHelperTest extends TestCase | |||
240 | public function getDescriptionsByType(): array | 278 | public function getDescriptionsByType(): array |
241 | { | 279 | { |
242 | return [ | 280 | return [ |
243 | [DailyPageHelper::DAY, $date = new \DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')], | 281 | [DailyPageHelper::DAY, $date = new DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')], |
244 | [DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F j, Y')], | 282 | [DailyPageHelper::DAY, $date = new DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F j, Y')], |
283 | [DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], | ||
284 | [DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], | ||
285 | [DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], | ||
286 | ]; | ||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Data provider for testGeDescriptionsByTypeNotIncludeRelative() test method. | ||
291 | */ | ||
292 | public function getDescriptionsByTypeNotIncludeRelative(): array | ||
293 | { | ||
294 | return [ | ||
295 | [DailyPageHelper::DAY, $date = new \DateTimeImmutable(), $date->format('F j, Y')], | ||
296 | [DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), $date->format('F j, Y')], | ||
245 | [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], | 297 | [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], |
246 | [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], | 298 | [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], |
247 | [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], | 299 | [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], |
@@ -249,7 +301,7 @@ class DailyPageHelperTest extends TestCase | |||
249 | } | 301 | } |
250 | 302 | ||
251 | /** | 303 | /** |
252 | * Data provider for testGetDescriptionsByType() test method. | 304 | * Data provider for testGetRssLengthsByType() test method. |
253 | */ | 305 | */ |
254 | public function getRssLengthsByType(): array | 306 | public function getRssLengthsByType(): array |
255 | { | 307 | { |
@@ -259,4 +311,31 @@ class DailyPageHelperTest extends TestCase | |||
259 | [DailyPageHelper::MONTH], | 311 | [DailyPageHelper::MONTH], |
260 | ]; | 312 | ]; |
261 | } | 313 | } |
314 | |||
315 | /** | ||
316 | * Data provider for testGetCacheDatePeriodByType() test method. | ||
317 | */ | ||
318 | public function getCacheDatePeriodByType(): array | ||
319 | { | ||
320 | return [ | ||
321 | [ | ||
322 | DailyPageHelper::DAY, | ||
323 | new DateTimeImmutable('2020-10-09 04:05:06'), | ||
324 | new \DateTime('2020-10-09 00:00:00'), | ||
325 | new \DateTime('2020-10-09 23:59:59'), | ||
326 | ], | ||
327 | [ | ||
328 | DailyPageHelper::WEEK, | ||
329 | new DateTimeImmutable('2020-10-09 04:05:06'), | ||
330 | new \DateTime('2020-10-05 00:00:00'), | ||
331 | new \DateTime('2020-10-11 23:59:59'), | ||
332 | ], | ||
333 | [ | ||
334 | DailyPageHelper::MONTH, | ||
335 | new DateTimeImmutable('2020-10-09 04:05:06'), | ||
336 | new \DateTime('2020-10-01 00:00:00'), | ||
337 | new \DateTime('2020-10-31 23:59:59'), | ||
338 | ], | ||
339 | ]; | ||
340 | } | ||
262 | } | 341 | } |
diff --git a/tests/plugins/test/test.php b/tests/plugins/test/test.php index 03be4f4e..34cd339e 100644 --- a/tests/plugins/test/test.php +++ b/tests/plugins/test/test.php | |||
@@ -27,3 +27,19 @@ function hook_test_error() | |||
27 | { | 27 | { |
28 | new Unknown(); | 28 | new Unknown(); |
29 | } | 29 | } |
30 | |||
31 | function test_register_routes(): array | ||
32 | { | ||
33 | return [ | ||
34 | [ | ||
35 | 'method' => 'GET', | ||
36 | 'route' => '/test', | ||
37 | 'callable' => 'getFunction', | ||
38 | ], | ||
39 | [ | ||
40 | 'method' => 'POST', | ||
41 | 'route' => '/custom', | ||
42 | 'callable' => 'postFunction', | ||
43 | ], | ||
44 | ]; | ||
45 | } | ||
diff --git a/tests/plugins/test_route_invalid/test_route_invalid.php b/tests/plugins/test_route_invalid/test_route_invalid.php new file mode 100644 index 00000000..0c5a5101 --- /dev/null +++ b/tests/plugins/test_route_invalid/test_route_invalid.php | |||
@@ -0,0 +1,12 @@ | |||
1 | <?php | ||
2 | |||
3 | function test_route_invalid_register_routes(): array | ||
4 | { | ||
5 | return [ | ||
6 | [ | ||
7 | 'method' => 'GET', | ||
8 | 'route' => 'not a route', | ||
9 | 'callable' => 'getFunction', | ||
10 | ], | ||
11 | ]; | ||
12 | } | ||
diff --git a/tpl/default/pluginscontent.html b/tpl/default/pluginscontent.html new file mode 100644 index 00000000..1e4f6b80 --- /dev/null +++ b/tpl/default/pluginscontent.html | |||
@@ -0,0 +1,13 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html{if="$language !== 'auto'"} lang="{$language}"{/if}> | ||
3 | <head> | ||
4 | {include="includes"} | ||
5 | </head> | ||
6 | <body> | ||
7 | {include="page.header"} | ||
8 | |||
9 | {$content} | ||
10 | |||
11 | {include="page.footer"} | ||
12 | </body> | ||
13 | </html> | ||
@@ -3052,9 +3052,9 @@ inherits@2.0.3: | |||
3052 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= | 3052 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= |
3053 | 3053 | ||
3054 | ini@^1.3.4, ini@^1.3.5: | 3054 | ini@^1.3.4, ini@^1.3.5: |
3055 | version "1.3.5" | 3055 | version "1.3.7" |
3056 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" | 3056 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" |
3057 | integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== | 3057 | integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== |
3058 | 3058 | ||
3059 | interpret@^1.4.0: | 3059 | interpret@^1.4.0: |
3060 | version "1.4.0" | 3060 | version "1.4.0" |