aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/api/ApiUtils.php21
-rw-r--r--application/api/controllers/Links.php12
-rw-r--r--application/bookmark/LinkUtils.php6
-rw-r--r--application/feed/CachedPage.php45
-rw-r--r--application/front/controller/admin/ShaarePublishController.php2
-rw-r--r--application/front/controller/visitor/DailyController.php5
-rw-r--r--application/helper/DailyPageHelper.php66
-rw-r--r--application/http/MetadataRetriever.php9
-rw-r--r--application/render/PageCacheManager.php14
-rw-r--r--tests/api/controllers/links/PostLinkTest.php48
-rw-r--r--tests/api/controllers/links/PutLinkTest.php48
-rw-r--r--tests/bookmark/LinkUtilsTest.php10
-rw-r--r--tests/feed/CachedPageTest.php57
-rw-r--r--tests/helper/DailyPageHelperTest.php94
-rw-r--r--tests/http/MetadataRetrieverTest.php2
15 files changed, 359 insertions, 80 deletions
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index 05a2840a..9228bb2d 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -91,13 +91,17 @@ class ApiUtils
91 * If no URL is provided, it will generate a local note URL. 91 * If no URL is provided, it will generate a local note URL.
92 * If no title is provided, it will use the URL as title. 92 * If no title is provided, it will use the URL as title.
93 * 93 *
94 * @param array|null $input Request Link. 94 * @param array|null $input Request Link.
95 * @param bool $defaultPrivate Setting defined if a bookmark is private by default. 95 * @param bool $defaultPrivate Setting defined if a bookmark is private by default.
96 * @param string $tagsSeparator Tags separator loaded from the config file.
96 * 97 *
97 * @return Bookmark instance. 98 * @return Bookmark instance.
98 */ 99 */
99 public static function buildBookmarkFromRequest(?array $input, bool $defaultPrivate): Bookmark 100 public static function buildBookmarkFromRequest(
100 { 101 ?array $input,
102 bool $defaultPrivate,
103 string $tagsSeparator
104 ): Bookmark {
101 $bookmark = new Bookmark(); 105 $bookmark = new Bookmark();
102 $url = ! empty($input['url']) ? cleanup_url($input['url']) : ''; 106 $url = ! empty($input['url']) ? cleanup_url($input['url']) : '';
103 if (isset($input['private'])) { 107 if (isset($input['private'])) {
@@ -109,6 +113,15 @@ class ApiUtils
109 $bookmark->setTitle(! empty($input['title']) ? $input['title'] : ''); 113 $bookmark->setTitle(! empty($input['title']) ? $input['title'] : '');
110 $bookmark->setUrl($url); 114 $bookmark->setUrl($url);
111 $bookmark->setDescription(! empty($input['description']) ? $input['description'] : ''); 115 $bookmark->setDescription(! empty($input['description']) ? $input['description'] : '');
116
117 // Be permissive with provided tags format
118 if (is_string($input['tags'] ?? null)) {
119 $input['tags'] = tags_str2array($input['tags'], $tagsSeparator);
120 }
121 if (is_array($input['tags'] ?? null) && count($input['tags']) === 1 && is_string($input['tags'][0])) {
122 $input['tags'] = tags_str2array($input['tags'][0], $tagsSeparator);
123 }
124
112 $bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []); 125 $bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []);
113 $bookmark->setPrivate($private); 126 $bookmark->setPrivate($private);
114 127
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php
index c379b962..b83b2260 100644
--- a/application/api/controllers/Links.php
+++ b/application/api/controllers/Links.php
@@ -117,7 +117,11 @@ class Links extends ApiController
117 public function postLink($request, $response) 117 public function postLink($request, $response)
118 { 118 {
119 $data = (array) ($request->getParsedBody() ?? []); 119 $data = (array) ($request->getParsedBody() ?? []);
120 $bookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links')); 120 $bookmark = ApiUtils::buildBookmarkFromRequest(
121 $data,
122 $this->conf->get('privacy.default_private_links'),
123 $this->conf->get('general.tags_separator', ' ')
124 );
121 // duplicate by URL, return 409 Conflict 125 // duplicate by URL, return 409 Conflict
122 if ( 126 if (
123 ! empty($bookmark->getUrl()) 127 ! empty($bookmark->getUrl())
@@ -158,7 +162,11 @@ class Links extends ApiController
158 $index = index_url($this->ci['environment']); 162 $index = index_url($this->ci['environment']);
159 $data = $request->getParsedBody(); 163 $data = $request->getParsedBody();
160 164
161 $requestBookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links')); 165 $requestBookmark = ApiUtils::buildBookmarkFromRequest(
166 $data,
167 $this->conf->get('privacy.default_private_links'),
168 $this->conf->get('general.tags_separator', ' ')
169 );
162 // duplicate URL on a different link, return 409 Conflict 170 // duplicate URL on a different link, return 409 Conflict
163 if ( 171 if (
164 ! empty($requestBookmark->getUrl()) 172 ! empty($requestBookmark->getUrl())
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/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
3declare(strict_types=1);
4
3namespace Shaarli\Feed; 5namespace Shaarli\Feed;
4 6
7use 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 */
8class CachedPage 12class 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/ShaarePublishController.php b/application/front/controller/admin/ShaarePublishController.php
index 4cbfcdc5..fb9cacc2 100644
--- a/application/front/controller/admin/ShaarePublishController.php
+++ b/application/front/controller/admin/ShaarePublishController.php
@@ -227,7 +227,7 @@ class ShaarePublishController extends ShaarliAdminController
227 227
228 protected function buildFormData(array $link, bool $isNew, Request $request): array 228 protected function buildFormData(array $link, bool $isNew, Request $request): array
229 { 229 {
230 $link['tags'] = strlen($link['tags']) > 0 230 $link['tags'] = $link['tags'] !== null && strlen($link['tags']) > 0
231 ? $link['tags'] . $this->container->conf->get('general.tags_separator', ' ') 231 ? $link['tags'] . $this->container->conf->get('general.tags_separator', ' ')
232 : $link['tags'] 232 : $link['tags']
233 ; 233 ;
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php
index 5ae89299..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) {
diff --git a/application/helper/DailyPageHelper.php b/application/helper/DailyPageHelper.php
index 9bdb7ba5..05f95812 100644
--- a/application/helper/DailyPageHelper.php
+++ b/application/helper/DailyPageHelper.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
4 4
5namespace Shaarli\Helper; 5namespace Shaarli\Helper;
6 6
7use DatePeriod;
8use DateTimeImmutable;
9use Exception;
7use Shaarli\Bookmark\Bookmark; 10use Shaarli\Bookmark\Bookmark;
8use Slim\Http\Request; 11use 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
@@ -161,7 +164,7 @@ class DailyPageHelper
161 * 164 *
162 * @return string Localized time period description 165 * @return string Localized time period description
163 * 166 *
164 * @throws \Exception Type not supported. 167 * @throws Exception Type not supported.
165 */ 168 */
166 public static function getDescriptionByType( 169 public static function getDescriptionByType(
167 string $type, 170 string $type,
@@ -183,7 +186,7 @@ class DailyPageHelper
183 } 186 }
184 return $out . format_date($requested, false); 187 return $out . format_date($requested, false);
185 default: 188 default:
186 throw new \Exception('Unsupported daily format type'); 189 throw new Exception('Unsupported daily format type');
187 } 190 }
188 } 191 }
189 192
@@ -194,7 +197,7 @@ class DailyPageHelper
194 * 197 *
195 * @return int number of elements 198 * @return int number of elements
196 * 199 *
197 * @throws \Exception Type not supported. 200 * @throws Exception Type not supported.
198 */ 201 */
199 public static function getRssLengthByType(string $type): int 202 public static function getRssLengthByType(string $type): int
200 { 203 {
@@ -206,7 +209,28 @@ class DailyPageHelper
206 case static::DAY: 209 case static::DAY:
207 return 30; // ~1 month 210 return 30; // ~1 month
208 default: 211 default:
209 throw new \Exception('Unsupported daily format type'); 212 throw new Exception('Unsupported daily format type');
210 } 213 }
211 } 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 }
212} 236}
diff --git a/application/http/MetadataRetriever.php b/application/http/MetadataRetriever.php
index 2e1401ec..cfc72583 100644
--- a/application/http/MetadataRetriever.php
+++ b/application/http/MetadataRetriever.php
@@ -60,10 +60,15 @@ class MetadataRetriever
60 $title = mb_convert_encoding($title, 'utf-8', $charset); 60 $title = mb_convert_encoding($title, 'utf-8', $charset);
61 } 61 }
62 62
63 return [ 63 return array_map([$this, 'cleanMetadata'], [
64 'title' => $title, 64 'title' => $title,
65 'description' => $description, 65 'description' => $description,
66 'tags' => $tags, 66 'tags' => $tags,
67 ]; 67 ]);
68 }
69
70 protected function cleanMetadata($data): ?string
71 {
72 return !is_string($data) || empty(trim($data)) ? null : trim($data);
68 } 73 }
69} 74}
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
3namespace Shaarli\Render; 3namespace Shaarli\Render;
4 4
5use DatePeriod;
5use Shaarli\Feed\CachedPage; 6use 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/tests/api/controllers/links/PostLinkTest.php b/tests/api/controllers/links/PostLinkTest.php
index e12f803b..f755e2d2 100644
--- a/tests/api/controllers/links/PostLinkTest.php
+++ b/tests/api/controllers/links/PostLinkTest.php
@@ -229,4 +229,52 @@ class PostLinkTest extends TestCase
229 \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) 229 \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
230 ); 230 );
231 } 231 }
232
233 /**
234 * Test link creation with a tag string provided
235 */
236 public function testPostLinkWithTagString(): void
237 {
238 $link = [
239 'tags' => 'one two',
240 ];
241 $env = Environment::mock([
242 'REQUEST_METHOD' => 'POST',
243 'CONTENT_TYPE' => 'application/json'
244 ]);
245
246 $request = Request::createFromEnvironment($env);
247 $request = $request->withParsedBody($link);
248 $response = $this->controller->postLink($request, new Response());
249
250 $this->assertEquals(201, $response->getStatusCode());
251 $this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]);
252 $data = json_decode((string) $response->getBody(), true);
253 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
254 $this->assertEquals(['one', 'two'], $data['tags']);
255 }
256
257 /**
258 * Test link creation with a tag string provided
259 */
260 public function testPostLinkWithTagString2(): void
261 {
262 $link = [
263 'tags' => ['one two'],
264 ];
265 $env = Environment::mock([
266 'REQUEST_METHOD' => 'POST',
267 'CONTENT_TYPE' => 'application/json'
268 ]);
269
270 $request = Request::createFromEnvironment($env);
271 $request = $request->withParsedBody($link);
272 $response = $this->controller->postLink($request, new Response());
273
274 $this->assertEquals(201, $response->getStatusCode());
275 $this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]);
276 $data = json_decode((string) $response->getBody(), true);
277 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
278 $this->assertEquals(['one', 'two'], $data['tags']);
279 }
232} 280}
diff --git a/tests/api/controllers/links/PutLinkTest.php b/tests/api/controllers/links/PutLinkTest.php
index 240ee323..fe24f2eb 100644
--- a/tests/api/controllers/links/PutLinkTest.php
+++ b/tests/api/controllers/links/PutLinkTest.php
@@ -233,4 +233,52 @@ class PutLinkTest extends \Shaarli\TestCase
233 233
234 $this->controller->putLink($request, new Response(), ['id' => -1]); 234 $this->controller->putLink($request, new Response(), ['id' => -1]);
235 } 235 }
236
237 /**
238 * Test link creation with a tag string provided
239 */
240 public function testPutLinkWithTagString(): void
241 {
242 $link = [
243 'tags' => 'one two',
244 ];
245 $id = '41';
246 $env = Environment::mock([
247 'REQUEST_METHOD' => 'PUT',
248 'CONTENT_TYPE' => 'application/json'
249 ]);
250
251 $request = Request::createFromEnvironment($env);
252 $request = $request->withParsedBody($link);
253 $response = $this->controller->putLink($request, new Response(), ['id' => $id]);
254
255 $this->assertEquals(200, $response->getStatusCode());
256 $data = json_decode((string) $response->getBody(), true);
257 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
258 $this->assertEquals(['one', 'two'], $data['tags']);
259 }
260
261 /**
262 * Test link creation with a tag string provided
263 */
264 public function testPutLinkWithTagString2(): void
265 {
266 $link = [
267 'tags' => ['one two'],
268 ];
269 $id = '41';
270 $env = Environment::mock([
271 'REQUEST_METHOD' => 'PUT',
272 'CONTENT_TYPE' => 'application/json'
273 ]);
274
275 $request = Request::createFromEnvironment($env);
276 $request = $request->withParsedBody($link);
277 $response = $this->controller->putLink($request, new Response(), ['id' => $id]);
278
279 $this->assertEquals(200, $response->getStatusCode());
280 $data = json_decode((string) $response->getBody(), true);
281 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
282 $this->assertEquals(['one', 'two'], $data['tags']);
283 }
236} 284}
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/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 6238e648..2d745800 100644
--- a/tests/helper/DailyPageHelperTest.php
+++ b/tests/helper/DailyPageHelperTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
4 4
5namespace Shaarli\Helper; 5namespace Shaarli\Helper;
6 6
7use DateTimeImmutable;
8use DateTimeInterface;
7use Shaarli\Bookmark\Bookmark; 9use Shaarli\Bookmark\Bookmark;
8use Shaarli\TestCase; 10use Shaarli\TestCase;
9use Slim\Http\Request; 11use 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);
@@ -139,7 +141,7 @@ class DailyPageHelperTest extends TestCase
139 $this->expectException(\Exception::class); 141 $this->expectException(\Exception::class);
140 $this->expectExceptionMessage('Unsupported daily format type'); 142 $this->expectExceptionMessage('Unsupported daily format type');
141 143
142 DailyPageHelper::getDescriptionByType('nope', new \DateTimeImmutable()); 144 DailyPageHelper::getDescriptionByType('nope', new DateTimeImmutable());
143 } 145 }
144 146
145 /** 147 /**
@@ -160,6 +162,29 @@ class DailyPageHelperTest extends TestCase
160 } 162 }
161 163
162 /** 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 /**
163 * Data provider for testExtractRequestedType() test method. 188 * Data provider for testExtractRequestedType() test method.
164 */ 189 */
165 public function getRequestedTypes(): array 190 public function getRequestedTypes(): array
@@ -229,9 +254,9 @@ class DailyPageHelperTest extends TestCase
229 public function getStartDatesByType(): array 254 public function getStartDatesByType(): array
230 { 255 {
231 return [ 256 return [
232 [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')],
233 [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')],
234 [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')],
235 ]; 260 ];
236 } 261 }
237 262
@@ -241,9 +266,9 @@ class DailyPageHelperTest extends TestCase
241 public function getEndDatesByType(): array 266 public function getEndDatesByType(): array
242 { 267 {
243 return [ 268 return [
244 [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')],
245 [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')],
246 [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')],
247 ]; 272 ];
248 } 273 }
249 274
@@ -253,11 +278,11 @@ class DailyPageHelperTest extends TestCase
253 public function getDescriptionsByType(): array 278 public function getDescriptionsByType(): array
254 { 279 {
255 return [ 280 return [
256 [DailyPageHelper::DAY, $date = new \DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')], 281 [DailyPageHelper::DAY, $date = new DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')],
257 [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')],
258 [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], 283 [DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'],
259 [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], 284 [DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'],
260 [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], 285 [DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'],
261 ]; 286 ];
262 } 287 }
263 288
@@ -276,7 +301,7 @@ class DailyPageHelperTest extends TestCase
276 } 301 }
277 302
278 /** 303 /**
279 * Data provider for testGetDescriptionsByType() test method. 304 * Data provider for testGetRssLengthsByType() test method.
280 */ 305 */
281 public function getRssLengthsByType(): array 306 public function getRssLengthsByType(): array
282 { 307 {
@@ -286,4 +311,31 @@ class DailyPageHelperTest extends TestCase
286 [DailyPageHelper::MONTH], 311 [DailyPageHelper::MONTH],
287 ]; 312 ];
288 } 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 }
289} 341}
diff --git a/tests/http/MetadataRetrieverTest.php b/tests/http/MetadataRetrieverTest.php
index 3c9eaa0e..cae65091 100644
--- a/tests/http/MetadataRetrieverTest.php
+++ b/tests/http/MetadataRetrieverTest.php
@@ -41,7 +41,7 @@ class MetadataRetrieverTest extends TestCase
41 $remoteCharset = 'utf-8'; 41 $remoteCharset = 'utf-8';
42 42
43 $expectedResult = [ 43 $expectedResult = [
44 'title' => $remoteTitle, 44 'title' => trim($remoteTitle),
45 'description' => $remoteDesc, 45 'description' => $remoteDesc,
46 'tags' => $remoteTags, 46 'tags' => $remoteTags,
47 ]; 47 ];