aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2020-10-02 17:50:59 +0200
committerArthurHoaro <arthur@hoa.ro>2020-10-13 13:50:11 +0200
commitefb7d21b52eb033530e80e5e49d175e6e3b031f4 (patch)
tree4f34052788a08be1a30cb88c3339ae14e0b7c4da
parent29c31b7ec6ca48ba37b7eb6da650931fd0cb7164 (diff)
downloadShaarli-efb7d21b52eb033530e80e5e49d175e6e3b031f4.tar.gz
Shaarli-efb7d21b52eb033530e80e5e49d175e6e3b031f4.tar.zst
Shaarli-efb7d21b52eb033530e80e5e49d175e6e3b031f4.zip
Add strict types for bookmarks management
Parameters typing and using strict types overall increase the codebase quality by enforcing the a given parameter will have the expected type. It also removes the need to unnecessary unit tests checking methods behavior with invalid input.
-rw-r--r--application/api/ApiUtils.php6
-rw-r--r--application/api/controllers/Links.php19
-rw-r--r--application/bookmark/Bookmark.php121
-rw-r--r--application/bookmark/BookmarkArray.php14
-rw-r--r--application/bookmark/BookmarkFileService.php66
-rw-r--r--application/bookmark/BookmarkFilter.php47
-rw-r--r--application/bookmark/BookmarkIO.php6
-rw-r--r--application/bookmark/BookmarkInitializer.php6
-rw-r--r--application/bookmark/BookmarkServiceInterface.php72
-rw-r--r--application/feed/FeedBuilder.php2
-rw-r--r--application/front/controller/admin/ThumbnailsController.php2
-rw-r--r--application/security/LoginManager.php2
-rw-r--r--tests/HistoryTest.php8
-rw-r--r--tests/bookmark/BookmarkArrayTest.php13
-rw-r--r--tests/bookmark/BookmarkFileServiceTest.php44
-rw-r--r--tests/bookmark/BookmarkTest.php38
-rw-r--r--tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php4
-rw-r--r--tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php20
-rw-r--r--tests/front/controller/admin/ThumbnailsControllerTest.php4
19 files changed, 209 insertions, 285 deletions
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index 4a6326f0..eb1ca9bc 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -89,12 +89,12 @@ class ApiUtils
89 * If no URL is provided, it will generate a local note URL. 89 * If no URL is provided, it will generate a local note URL.
90 * If no title is provided, it will use the URL as title. 90 * If no title is provided, it will use the URL as title.
91 * 91 *
92 * @param array $input Request Link. 92 * @param array|null $input Request Link.
93 * @param bool $defaultPrivate Request Link. 93 * @param bool $defaultPrivate Setting defined if a bookmark is private by default.
94 * 94 *
95 * @return Bookmark instance. 95 * @return Bookmark instance.
96 */ 96 */
97 public static function buildBookmarkFromRequest($input, $defaultPrivate): Bookmark 97 public static function buildBookmarkFromRequest(?array $input, bool $defaultPrivate): Bookmark
98 { 98 {
99 $bookmark = new Bookmark(); 99 $bookmark = new Bookmark();
100 $url = ! empty($input['url']) ? cleanup_url($input['url']) : ''; 100 $url = ! empty($input['url']) ? cleanup_url($input['url']) : '';
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php
index 778097fd..73a1b84e 100644
--- a/application/api/controllers/Links.php
+++ b/application/api/controllers/Links.php
@@ -96,11 +96,12 @@ class Links extends ApiController
96 */ 96 */
97 public function getLink($request, $response, $args) 97 public function getLink($request, $response, $args)
98 { 98 {
99 if (!$this->bookmarkService->exists($args['id'])) { 99 $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null;
100 if ($id === null || ! $this->bookmarkService->exists($id)) {
100 throw new ApiLinkNotFoundException(); 101 throw new ApiLinkNotFoundException();
101 } 102 }
102 $index = index_url($this->ci['environment']); 103 $index = index_url($this->ci['environment']);
103 $out = ApiUtils::formatLink($this->bookmarkService->get($args['id']), $index); 104 $out = ApiUtils::formatLink($this->bookmarkService->get($id), $index);
104 105
105 return $response->withJson($out, 200, $this->jsonStyle); 106 return $response->withJson($out, 200, $this->jsonStyle);
106 } 107 }
@@ -115,7 +116,7 @@ class Links extends ApiController
115 */ 116 */
116 public function postLink($request, $response) 117 public function postLink($request, $response)
117 { 118 {
118 $data = $request->getParsedBody(); 119 $data = (array) ($request->getParsedBody() ?? []);
119 $bookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links')); 120 $bookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links'));
120 // duplicate by URL, return 409 Conflict 121 // duplicate by URL, return 409 Conflict
121 if (! empty($bookmark->getUrl()) 122 if (! empty($bookmark->getUrl())
@@ -148,7 +149,8 @@ class Links extends ApiController
148 */ 149 */
149 public function putLink($request, $response, $args) 150 public function putLink($request, $response, $args)
150 { 151 {
151 if (! $this->bookmarkService->exists($args['id'])) { 152 $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null;
153 if ($id === null || !$this->bookmarkService->exists($id)) {
152 throw new ApiLinkNotFoundException(); 154 throw new ApiLinkNotFoundException();
153 } 155 }
154 156
@@ -159,7 +161,7 @@ class Links extends ApiController
159 // duplicate URL on a different link, return 409 Conflict 161 // duplicate URL on a different link, return 409 Conflict
160 if (! empty($requestBookmark->getUrl()) 162 if (! empty($requestBookmark->getUrl())
161 && ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl())) 163 && ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl()))
162 && $dup->getId() != $args['id'] 164 && $dup->getId() != $id
163 ) { 165 ) {
164 return $response->withJson( 166 return $response->withJson(
165 ApiUtils::formatLink($dup, $index), 167 ApiUtils::formatLink($dup, $index),
@@ -168,7 +170,7 @@ class Links extends ApiController
168 ); 170 );
169 } 171 }
170 172
171 $responseBookmark = $this->bookmarkService->get($args['id']); 173 $responseBookmark = $this->bookmarkService->get($id);
172 $responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark); 174 $responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark);
173 $this->bookmarkService->set($responseBookmark); 175 $this->bookmarkService->set($responseBookmark);
174 176
@@ -189,10 +191,11 @@ class Links extends ApiController
189 */ 191 */
190 public function deleteLink($request, $response, $args) 192 public function deleteLink($request, $response, $args)
191 { 193 {
192 if (! $this->bookmarkService->exists($args['id'])) { 194 $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null;
195 if ($id === null || !$this->bookmarkService->exists($id)) {
193 throw new ApiLinkNotFoundException(); 196 throw new ApiLinkNotFoundException();
194 } 197 }
195 $bookmark = $this->bookmarkService->get($args['id']); 198 $bookmark = $this->bookmarkService->get($id);
196 $this->bookmarkService->remove($bookmark); 199 $this->bookmarkService->remove($bookmark);
197 200
198 return $response->withStatus(204); 201 return $response->withStatus(204);
diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php
index 1beb8be2..fa45d2fc 100644
--- a/application/bookmark/Bookmark.php
+++ b/application/bookmark/Bookmark.php
@@ -1,5 +1,7 @@
1<?php 1<?php
2 2
3declare(strict_types=1);
4
3namespace Shaarli\Bookmark; 5namespace Shaarli\Bookmark;
4 6
5use DateTime; 7use DateTime;
@@ -59,25 +61,25 @@ class Bookmark
59 * 61 *
60 * @return $this 62 * @return $this
61 */ 63 */
62 public function fromArray($data) 64 public function fromArray(array $data): Bookmark
63 { 65 {
64 $this->id = $data['id']; 66 $this->id = $data['id'] ?? null;
65 $this->shortUrl = $data['shorturl']; 67 $this->shortUrl = $data['shorturl'] ?? null;
66 $this->url = $data['url']; 68 $this->url = $data['url'] ?? null;
67 $this->title = $data['title']; 69 $this->title = $data['title'] ?? null;
68 $this->description = $data['description']; 70 $this->description = $data['description'] ?? null;
69 $this->thumbnail = isset($data['thumbnail']) ? $data['thumbnail'] : null; 71 $this->thumbnail = $data['thumbnail'] ?? null;
70 $this->sticky = isset($data['sticky']) ? $data['sticky'] : false; 72 $this->sticky = $data['sticky'] ?? false;
71 $this->created = $data['created']; 73 $this->created = $data['created'] ?? null;
72 if (is_array($data['tags'])) { 74 if (is_array($data['tags'])) {
73 $this->tags = $data['tags']; 75 $this->tags = $data['tags'];
74 } else { 76 } else {
75 $this->tags = preg_split('/\s+/', $data['tags'], -1, PREG_SPLIT_NO_EMPTY); 77 $this->tags = preg_split('/\s+/', $data['tags'] ?? '', -1, PREG_SPLIT_NO_EMPTY);
76 } 78 }
77 if (! empty($data['updated'])) { 79 if (! empty($data['updated'])) {
78 $this->updated = $data['updated']; 80 $this->updated = $data['updated'];
79 } 81 }
80 $this->private = $data['private'] ? true : false; 82 $this->private = ($data['private'] ?? false) ? true : false;
81 83
82 return $this; 84 return $this;
83 } 85 }
@@ -95,13 +97,12 @@ class Bookmark
95 * 97 *
96 * @throws InvalidBookmarkException 98 * @throws InvalidBookmarkException
97 */ 99 */
98 public function validate() 100 public function validate(): void
99 { 101 {
100 if ($this->id === null 102 if ($this->id === null
101 || ! is_int($this->id) 103 || ! is_int($this->id)
102 || empty($this->shortUrl) 104 || empty($this->shortUrl)
103 || empty($this->created) 105 || empty($this->created)
104 || ! $this->created instanceof DateTimeInterface
105 ) { 106 ) {
106 throw new InvalidBookmarkException($this); 107 throw new InvalidBookmarkException($this);
107 } 108 }
@@ -119,11 +120,11 @@ class Bookmark
119 * - created: with the current datetime 120 * - created: with the current datetime
120 * - shortUrl: with a generated small hash from the date and the given ID 121 * - shortUrl: with a generated small hash from the date and the given ID
121 * 122 *
122 * @param int $id 123 * @param int|null $id
123 * 124 *
124 * @return Bookmark 125 * @return Bookmark
125 */ 126 */
126 public function setId($id) 127 public function setId(?int $id): Bookmark
127 { 128 {
128 $this->id = $id; 129 $this->id = $id;
129 if (empty($this->created)) { 130 if (empty($this->created)) {
@@ -139,9 +140,9 @@ class Bookmark
139 /** 140 /**
140 * Get the Id. 141 * Get the Id.
141 * 142 *
142 * @return int 143 * @return int|null
143 */ 144 */
144 public function getId() 145 public function getId(): ?int
145 { 146 {
146 return $this->id; 147 return $this->id;
147 } 148 }
@@ -149,9 +150,9 @@ class Bookmark
149 /** 150 /**
150 * Get the ShortUrl. 151 * Get the ShortUrl.
151 * 152 *
152 * @return string 153 * @return string|null
153 */ 154 */
154 public function getShortUrl() 155 public function getShortUrl(): ?string
155 { 156 {
156 return $this->shortUrl; 157 return $this->shortUrl;
157 } 158 }
@@ -159,9 +160,9 @@ class Bookmark
159 /** 160 /**
160 * Get the Url. 161 * Get the Url.
161 * 162 *
162 * @return string 163 * @return string|null
163 */ 164 */
164 public function getUrl() 165 public function getUrl(): ?string
165 { 166 {
166 return $this->url; 167 return $this->url;
167 } 168 }
@@ -171,7 +172,7 @@ class Bookmark
171 * 172 *
172 * @return string 173 * @return string
173 */ 174 */
174 public function getTitle() 175 public function getTitle(): ?string
175 { 176 {
176 return $this->title; 177 return $this->title;
177 } 178 }
@@ -181,7 +182,7 @@ class Bookmark
181 * 182 *
182 * @return string 183 * @return string
183 */ 184 */
184 public function getDescription() 185 public function getDescription(): string
185 { 186 {
186 return ! empty($this->description) ? $this->description : ''; 187 return ! empty($this->description) ? $this->description : '';
187 } 188 }
@@ -191,7 +192,7 @@ class Bookmark
191 * 192 *
192 * @return DateTimeInterface 193 * @return DateTimeInterface
193 */ 194 */
194 public function getCreated() 195 public function getCreated(): ?DateTimeInterface
195 { 196 {
196 return $this->created; 197 return $this->created;
197 } 198 }
@@ -201,7 +202,7 @@ class Bookmark
201 * 202 *
202 * @return DateTimeInterface 203 * @return DateTimeInterface
203 */ 204 */
204 public function getUpdated() 205 public function getUpdated(): ?DateTimeInterface
205 { 206 {
206 return $this->updated; 207 return $this->updated;
207 } 208 }
@@ -209,11 +210,11 @@ class Bookmark
209 /** 210 /**
210 * Set the ShortUrl. 211 * Set the ShortUrl.
211 * 212 *
212 * @param string $shortUrl 213 * @param string|null $shortUrl
213 * 214 *
214 * @return Bookmark 215 * @return Bookmark
215 */ 216 */
216 public function setShortUrl($shortUrl) 217 public function setShortUrl(?string $shortUrl): Bookmark
217 { 218 {
218 $this->shortUrl = $shortUrl; 219 $this->shortUrl = $shortUrl;
219 220
@@ -223,14 +224,14 @@ class Bookmark
223 /** 224 /**
224 * Set the Url. 225 * Set the Url.
225 * 226 *
226 * @param string $url 227 * @param string|null $url
227 * @param array $allowedProtocols 228 * @param string[] $allowedProtocols
228 * 229 *
229 * @return Bookmark 230 * @return Bookmark
230 */ 231 */
231 public function setUrl($url, $allowedProtocols = []) 232 public function setUrl(?string $url, array $allowedProtocols = []): Bookmark
232 { 233 {
233 $url = trim($url); 234 $url = $url !== null ? trim($url) : '';
234 if (! empty($url)) { 235 if (! empty($url)) {
235 $url = whitelist_protocols($url, $allowedProtocols); 236 $url = whitelist_protocols($url, $allowedProtocols);
236 } 237 }
@@ -242,13 +243,13 @@ class Bookmark
242 /** 243 /**
243 * Set the Title. 244 * Set the Title.
244 * 245 *
245 * @param string $title 246 * @param string|null $title
246 * 247 *
247 * @return Bookmark 248 * @return Bookmark
248 */ 249 */
249 public function setTitle($title) 250 public function setTitle(?string $title): Bookmark
250 { 251 {
251 $this->title = trim($title); 252 $this->title = $title !== null ? trim($title) : '';
252 253
253 return $this; 254 return $this;
254 } 255 }
@@ -256,11 +257,11 @@ class Bookmark
256 /** 257 /**
257 * Set the Description. 258 * Set the Description.
258 * 259 *
259 * @param string $description 260 * @param string|null $description
260 * 261 *
261 * @return Bookmark 262 * @return Bookmark
262 */ 263 */
263 public function setDescription($description) 264 public function setDescription(?string $description): Bookmark
264 { 265 {
265 $this->description = $description; 266 $this->description = $description;
266 267
@@ -271,11 +272,11 @@ class Bookmark
271 * Set the Created. 272 * Set the Created.
272 * Note: you shouldn't set this manually except for special cases (like bookmark import) 273 * Note: you shouldn't set this manually except for special cases (like bookmark import)
273 * 274 *
274 * @param DateTimeInterface $created 275 * @param DateTimeInterface|null $created
275 * 276 *
276 * @return Bookmark 277 * @return Bookmark
277 */ 278 */
278 public function setCreated($created) 279 public function setCreated(?DateTimeInterface $created): Bookmark
279 { 280 {
280 $this->created = $created; 281 $this->created = $created;
281 282
@@ -285,11 +286,11 @@ class Bookmark
285 /** 286 /**
286 * Set the Updated. 287 * Set the Updated.
287 * 288 *
288 * @param DateTimeInterface $updated 289 * @param DateTimeInterface|null $updated
289 * 290 *
290 * @return Bookmark 291 * @return Bookmark
291 */ 292 */
292 public function setUpdated($updated) 293 public function setUpdated(?DateTimeInterface $updated): Bookmark
293 { 294 {
294 $this->updated = $updated; 295 $this->updated = $updated;
295 296
@@ -301,7 +302,7 @@ class Bookmark
301 * 302 *
302 * @return bool 303 * @return bool
303 */ 304 */
304 public function isPrivate() 305 public function isPrivate(): bool
305 { 306 {
306 return $this->private ? true : false; 307 return $this->private ? true : false;
307 } 308 }
@@ -309,11 +310,11 @@ class Bookmark
309 /** 310 /**
310 * Set the Private. 311 * Set the Private.
311 * 312 *
312 * @param bool $private 313 * @param bool|null $private
313 * 314 *
314 * @return Bookmark 315 * @return Bookmark
315 */ 316 */
316 public function setPrivate($private) 317 public function setPrivate(?bool $private): Bookmark
317 { 318 {
318 $this->private = $private ? true : false; 319 $this->private = $private ? true : false;
319 320
@@ -323,9 +324,9 @@ class Bookmark
323 /** 324 /**
324 * Get the Tags. 325 * Get the Tags.
325 * 326 *
326 * @return array 327 * @return string[]
327 */ 328 */
328 public function getTags() 329 public function getTags(): array
329 { 330 {
330 return is_array($this->tags) ? $this->tags : []; 331 return is_array($this->tags) ? $this->tags : [];
331 } 332 }
@@ -333,13 +334,13 @@ class Bookmark
333 /** 334 /**
334 * Set the Tags. 335 * Set the Tags.
335 * 336 *
336 * @param array $tags 337 * @param string[]|null $tags
337 * 338 *
338 * @return Bookmark 339 * @return Bookmark
339 */ 340 */
340 public function setTags($tags) 341 public function setTags(?array $tags): Bookmark
341 { 342 {
342 $this->setTagsString(implode(' ', $tags)); 343 $this->setTagsString(implode(' ', $tags ?? []));
343 344
344 return $this; 345 return $this;
345 } 346 }
@@ -357,11 +358,11 @@ class Bookmark
357 /** 358 /**
358 * Set the Thumbnail. 359 * Set the Thumbnail.
359 * 360 *
360 * @param string|bool $thumbnail Thumbnail's URL - false if no thumbnail could be found 361 * @param string|bool|null $thumbnail Thumbnail's URL - false if no thumbnail could be found
361 * 362 *
362 * @return Bookmark 363 * @return Bookmark
363 */ 364 */
364 public function setThumbnail($thumbnail) 365 public function setThumbnail($thumbnail): Bookmark
365 { 366 {
366 $this->thumbnail = $thumbnail; 367 $this->thumbnail = $thumbnail;
367 368
@@ -373,7 +374,7 @@ class Bookmark
373 * 374 *
374 * @return bool 375 * @return bool
375 */ 376 */
376 public function isSticky() 377 public function isSticky(): bool
377 { 378 {
378 return $this->sticky ? true : false; 379 return $this->sticky ? true : false;
379 } 380 }
@@ -381,11 +382,11 @@ class Bookmark
381 /** 382 /**
382 * Set the Sticky. 383 * Set the Sticky.
383 * 384 *
384 * @param bool $sticky 385 * @param bool|null $sticky
385 * 386 *
386 * @return Bookmark 387 * @return Bookmark
387 */ 388 */
388 public function setSticky($sticky) 389 public function setSticky(?bool $sticky): Bookmark
389 { 390 {
390 $this->sticky = $sticky ? true : false; 391 $this->sticky = $sticky ? true : false;
391 392
@@ -395,7 +396,7 @@ class Bookmark
395 /** 396 /**
396 * @return string Bookmark's tags as a string, separated by a space 397 * @return string Bookmark's tags as a string, separated by a space
397 */ 398 */
398 public function getTagsString() 399 public function getTagsString(): string
399 { 400 {
400 return implode(' ', $this->getTags()); 401 return implode(' ', $this->getTags());
401 } 402 }
@@ -403,7 +404,7 @@ class Bookmark
403 /** 404 /**
404 * @return bool 405 * @return bool
405 */ 406 */
406 public function isNote() 407 public function isNote(): bool
407 { 408 {
408 // We check empty value to get a valid result if the link has not been saved yet 409 // We check empty value to get a valid result if the link has not been saved yet
409 return empty($this->url) || startsWith($this->url, '/shaare/') || $this->url[0] === '?'; 410 return empty($this->url) || startsWith($this->url, '/shaare/') || $this->url[0] === '?';
@@ -416,14 +417,14 @@ class Bookmark
416 * - multiple spaces will be removed 417 * - multiple spaces will be removed
417 * - trailing dash in tags will be removed 418 * - trailing dash in tags will be removed
418 * 419 *
419 * @param string $tags 420 * @param string|null $tags
420 * 421 *
421 * @return $this 422 * @return $this
422 */ 423 */
423 public function setTagsString($tags) 424 public function setTagsString(?string $tags): Bookmark
424 { 425 {
425 // Remove first '-' char in tags. 426 // Remove first '-' char in tags.
426 $tags = preg_replace('/(^| )\-/', '$1', $tags); 427 $tags = preg_replace('/(^| )\-/', '$1', $tags ?? '');
427 // Explode all tags separted by spaces or commas 428 // Explode all tags separted by spaces or commas
428 $tags = preg_split('/[\s,]+/', $tags); 429 $tags = preg_split('/[\s,]+/', $tags);
429 // Remove eventual empty values 430 // Remove eventual empty values
@@ -440,7 +441,7 @@ class Bookmark
440 * @param string $fromTag 441 * @param string $fromTag
441 * @param string $toTag 442 * @param string $toTag
442 */ 443 */
443 public function renameTag($fromTag, $toTag) 444 public function renameTag(string $fromTag, string $toTag): void
444 { 445 {
445 if (($pos = array_search($fromTag, $this->tags)) !== false) { 446 if (($pos = array_search($fromTag, $this->tags)) !== false) {
446 $this->tags[$pos] = trim($toTag); 447 $this->tags[$pos] = trim($toTag);
@@ -452,7 +453,7 @@ class Bookmark
452 * 453 *
453 * @param string $tag 454 * @param string $tag
454 */ 455 */
455 public function deleteTag($tag) 456 public function deleteTag(string $tag): void
456 { 457 {
457 if (($pos = array_search($tag, $this->tags)) !== false) { 458 if (($pos = array_search($tag, $this->tags)) !== false) {
458 unset($this->tags[$pos]); 459 unset($this->tags[$pos]);
diff --git a/application/bookmark/BookmarkArray.php b/application/bookmark/BookmarkArray.php
index 3bd5eb20..67bb3b73 100644
--- a/application/bookmark/BookmarkArray.php
+++ b/application/bookmark/BookmarkArray.php
@@ -1,5 +1,7 @@
1<?php 1<?php
2 2
3declare(strict_types=1);
4
3namespace Shaarli\Bookmark; 5namespace Shaarli\Bookmark;
4 6
5use Shaarli\Bookmark\Exception\InvalidBookmarkException; 7use Shaarli\Bookmark\Exception\InvalidBookmarkException;
@@ -187,13 +189,13 @@ class BookmarkArray implements \Iterator, \Countable, \ArrayAccess
187 /** 189 /**
188 * Returns a bookmark offset in bookmarks array from its unique ID. 190 * Returns a bookmark offset in bookmarks array from its unique ID.
189 * 191 *
190 * @param int $id Persistent ID of a bookmark. 192 * @param int|null $id Persistent ID of a bookmark.
191 * 193 *
192 * @return int Real offset in local array, or null if doesn't exist. 194 * @return int Real offset in local array, or null if doesn't exist.
193 */ 195 */
194 protected function getBookmarkOffset($id) 196 protected function getBookmarkOffset(?int $id): ?int
195 { 197 {
196 if (isset($this->ids[$id])) { 198 if ($id !== null && isset($this->ids[$id])) {
197 return $this->ids[$id]; 199 return $this->ids[$id];
198 } 200 }
199 return null; 201 return null;
@@ -205,7 +207,7 @@ class BookmarkArray implements \Iterator, \Countable, \ArrayAccess
205 * 207 *
206 * @return int next ID. 208 * @return int next ID.
207 */ 209 */
208 public function getNextId() 210 public function getNextId(): int
209 { 211 {
210 if (!empty($this->ids)) { 212 if (!empty($this->ids)) {
211 return max(array_keys($this->ids)) + 1; 213 return max(array_keys($this->ids)) + 1;
@@ -214,11 +216,11 @@ class BookmarkArray implements \Iterator, \Countable, \ArrayAccess
214 } 216 }
215 217
216 /** 218 /**
217 * @param $url 219 * @param string $url
218 * 220 *
219 * @return Bookmark|null 221 * @return Bookmark|null
220 */ 222 */
221 public function getByUrl($url) 223 public function getByUrl(string $url): ?Bookmark
222 { 224 {
223 if (! empty($url) 225 if (! empty($url)
224 && isset($this->urls[$url]) 226 && isset($this->urls[$url])
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php
index 1ba00712..804b2520 100644
--- a/application/bookmark/BookmarkFileService.php
+++ b/application/bookmark/BookmarkFileService.php
@@ -1,9 +1,10 @@
1<?php 1<?php
2 2
3declare(strict_types=1);
3 4
4namespace Shaarli\Bookmark; 5namespace Shaarli\Bookmark;
5 6
6 7use DateTime;
7use Exception; 8use Exception;
8use malkusch\lock\mutex\Mutex; 9use malkusch\lock\mutex\Mutex;
9use Shaarli\Bookmark\Exception\BookmarkNotFoundException; 10use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
@@ -54,7 +55,7 @@ class BookmarkFileService implements BookmarkServiceInterface
54 /** 55 /**
55 * @inheritDoc 56 * @inheritDoc
56 */ 57 */
57 public function __construct(ConfigManager $conf, History $history, Mutex $mutex, $isLoggedIn) 58 public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
58 { 59 {
59 $this->conf = $conf; 60 $this->conf = $conf;
60 $this->history = $history; 61 $this->history = $history;
@@ -96,7 +97,7 @@ class BookmarkFileService implements BookmarkServiceInterface
96 /** 97 /**
97 * @inheritDoc 98 * @inheritDoc
98 */ 99 */
99 public function findByHash($hash) 100 public function findByHash(string $hash): Bookmark
100 { 101 {
101 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash); 102 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash);
102 // PHP 7.3 introduced array_key_first() to avoid this hack 103 // PHP 7.3 introduced array_key_first() to avoid this hack
@@ -111,7 +112,7 @@ class BookmarkFileService implements BookmarkServiceInterface
111 /** 112 /**
112 * @inheritDoc 113 * @inheritDoc
113 */ 114 */
114 public function findByUrl($url) 115 public function findByUrl(string $url): ?Bookmark
115 { 116 {
116 return $this->bookmarks->getByUrl($url); 117 return $this->bookmarks->getByUrl($url);
117 } 118 }
@@ -120,10 +121,10 @@ class BookmarkFileService implements BookmarkServiceInterface
120 * @inheritDoc 121 * @inheritDoc
121 */ 122 */
122 public function search( 123 public function search(
123 $request = [], 124 array $request = [],
124 $visibility = null, 125 string $visibility = null,
125 $caseSensitive = false, 126 bool $caseSensitive = false,
126 $untaggedOnly = false, 127 bool $untaggedOnly = false,
127 bool $ignoreSticky = false 128 bool $ignoreSticky = false
128 ) { 129 ) {
129 if ($visibility === null) { 130 if ($visibility === null) {
@@ -131,8 +132,8 @@ class BookmarkFileService implements BookmarkServiceInterface
131 } 132 }
132 133
133 // Filter bookmark database according to parameters. 134 // Filter bookmark database according to parameters.
134 $searchtags = isset($request['searchtags']) ? $request['searchtags'] : ''; 135 $searchTags = isset($request['searchtags']) ? $request['searchtags'] : '';
135 $searchterm = isset($request['searchterm']) ? $request['searchterm'] : ''; 136 $searchTerm = isset($request['searchterm']) ? $request['searchterm'] : '';
136 137
137 if ($ignoreSticky) { 138 if ($ignoreSticky) {
138 $this->bookmarks->reorder('DESC', true); 139 $this->bookmarks->reorder('DESC', true);
@@ -140,7 +141,7 @@ class BookmarkFileService implements BookmarkServiceInterface
140 141
141 return $this->bookmarkFilter->filter( 142 return $this->bookmarkFilter->filter(
142 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, 143 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
143 [$searchtags, $searchterm], 144 [$searchTags, $searchTerm],
144 $caseSensitive, 145 $caseSensitive,
145 $visibility, 146 $visibility,
146 $untaggedOnly 147 $untaggedOnly
@@ -150,7 +151,7 @@ class BookmarkFileService implements BookmarkServiceInterface
150 /** 151 /**
151 * @inheritDoc 152 * @inheritDoc
152 */ 153 */
153 public function get($id, $visibility = null) 154 public function get(int $id, string $visibility = null): Bookmark
154 { 155 {
155 if (! isset($this->bookmarks[$id])) { 156 if (! isset($this->bookmarks[$id])) {
156 throw new BookmarkNotFoundException(); 157 throw new BookmarkNotFoundException();
@@ -173,20 +174,17 @@ class BookmarkFileService implements BookmarkServiceInterface
173 /** 174 /**
174 * @inheritDoc 175 * @inheritDoc
175 */ 176 */
176 public function set($bookmark, $save = true) 177 public function set(Bookmark $bookmark, bool $save = true): Bookmark
177 { 178 {
178 if (true !== $this->isLoggedIn) { 179 if (true !== $this->isLoggedIn) {
179 throw new Exception(t('You\'re not authorized to alter the datastore')); 180 throw new Exception(t('You\'re not authorized to alter the datastore'));
180 } 181 }
181 if (! $bookmark instanceof Bookmark) {
182 throw new Exception(t('Provided data is invalid'));
183 }
184 if (! isset($this->bookmarks[$bookmark->getId()])) { 182 if (! isset($this->bookmarks[$bookmark->getId()])) {
185 throw new BookmarkNotFoundException(); 183 throw new BookmarkNotFoundException();
186 } 184 }
187 $bookmark->validate(); 185 $bookmark->validate();
188 186
189 $bookmark->setUpdated(new \DateTime()); 187 $bookmark->setUpdated(new DateTime());
190 $this->bookmarks[$bookmark->getId()] = $bookmark; 188 $this->bookmarks[$bookmark->getId()] = $bookmark;
191 if ($save === true) { 189 if ($save === true) {
192 $this->save(); 190 $this->save();
@@ -198,15 +196,12 @@ class BookmarkFileService implements BookmarkServiceInterface
198 /** 196 /**
199 * @inheritDoc 197 * @inheritDoc
200 */ 198 */
201 public function add($bookmark, $save = true) 199 public function add(Bookmark $bookmark, bool $save = true): Bookmark
202 { 200 {
203 if (true !== $this->isLoggedIn) { 201 if (true !== $this->isLoggedIn) {
204 throw new Exception(t('You\'re not authorized to alter the datastore')); 202 throw new Exception(t('You\'re not authorized to alter the datastore'));
205 } 203 }
206 if (! $bookmark instanceof Bookmark) { 204 if (!empty($bookmark->getId())) {
207 throw new Exception(t('Provided data is invalid'));
208 }
209 if (! empty($bookmark->getId())) {
210 throw new Exception(t('This bookmarks already exists')); 205 throw new Exception(t('This bookmarks already exists'));
211 } 206 }
212 $bookmark->setId($this->bookmarks->getNextId()); 207 $bookmark->setId($this->bookmarks->getNextId());
@@ -223,14 +218,11 @@ class BookmarkFileService implements BookmarkServiceInterface
223 /** 218 /**
224 * @inheritDoc 219 * @inheritDoc
225 */ 220 */
226 public function addOrSet($bookmark, $save = true) 221 public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark
227 { 222 {
228 if (true !== $this->isLoggedIn) { 223 if (true !== $this->isLoggedIn) {
229 throw new Exception(t('You\'re not authorized to alter the datastore')); 224 throw new Exception(t('You\'re not authorized to alter the datastore'));
230 } 225 }
231 if (! $bookmark instanceof Bookmark) {
232 throw new Exception('Provided data is invalid');
233 }
234 if ($bookmark->getId() === null) { 226 if ($bookmark->getId() === null) {
235 return $this->add($bookmark, $save); 227 return $this->add($bookmark, $save);
236 } 228 }
@@ -240,14 +232,11 @@ class BookmarkFileService implements BookmarkServiceInterface
240 /** 232 /**
241 * @inheritDoc 233 * @inheritDoc
242 */ 234 */
243 public function remove($bookmark, $save = true) 235 public function remove(Bookmark $bookmark, bool $save = true): void
244 { 236 {
245 if (true !== $this->isLoggedIn) { 237 if (true !== $this->isLoggedIn) {
246 throw new Exception(t('You\'re not authorized to alter the datastore')); 238 throw new Exception(t('You\'re not authorized to alter the datastore'));
247 } 239 }
248 if (! $bookmark instanceof Bookmark) {
249 throw new Exception(t('Provided data is invalid'));
250 }
251 if (! isset($this->bookmarks[$bookmark->getId()])) { 240 if (! isset($this->bookmarks[$bookmark->getId()])) {
252 throw new BookmarkNotFoundException(); 241 throw new BookmarkNotFoundException();
253 } 242 }
@@ -262,7 +251,7 @@ class BookmarkFileService implements BookmarkServiceInterface
262 /** 251 /**
263 * @inheritDoc 252 * @inheritDoc
264 */ 253 */
265 public function exists($id, $visibility = null) 254 public function exists(int $id, string $visibility = null): bool
266 { 255 {
267 if (! isset($this->bookmarks[$id])) { 256 if (! isset($this->bookmarks[$id])) {
268 return false; 257 return false;
@@ -285,7 +274,7 @@ class BookmarkFileService implements BookmarkServiceInterface
285 /** 274 /**
286 * @inheritDoc 275 * @inheritDoc
287 */ 276 */
288 public function count($visibility = null) 277 public function count(string $visibility = null): int
289 { 278 {
290 return count($this->search([], $visibility)); 279 return count($this->search([], $visibility));
291 } 280 }
@@ -293,7 +282,7 @@ class BookmarkFileService implements BookmarkServiceInterface
293 /** 282 /**
294 * @inheritDoc 283 * @inheritDoc
295 */ 284 */
296 public function save() 285 public function save(): void
297 { 286 {
298 if (true !== $this->isLoggedIn) { 287 if (true !== $this->isLoggedIn) {
299 // TODO: raise an Exception instead 288 // TODO: raise an Exception instead
@@ -308,7 +297,7 @@ class BookmarkFileService implements BookmarkServiceInterface
308 /** 297 /**
309 * @inheritDoc 298 * @inheritDoc
310 */ 299 */
311 public function bookmarksCountPerTag($filteringTags = [], $visibility = null) 300 public function bookmarksCountPerTag(array $filteringTags = [], string $visibility = null): array
312 { 301 {
313 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility); 302 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
314 $tags = []; 303 $tags = [];
@@ -344,13 +333,14 @@ class BookmarkFileService implements BookmarkServiceInterface
344 $keys = array_keys($tags); 333 $keys = array_keys($tags);
345 $tmpTags = array_combine($keys, $keys); 334 $tmpTags = array_combine($keys, $keys);
346 array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags); 335 array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
336
347 return $tags; 337 return $tags;
348 } 338 }
349 339
350 /** 340 /**
351 * @inheritDoc 341 * @inheritDoc
352 */ 342 */
353 public function days() 343 public function days(): array
354 { 344 {
355 $bookmarkDays = []; 345 $bookmarkDays = [];
356 foreach ($this->search() as $bookmark) { 346 foreach ($this->search() as $bookmark) {
@@ -365,7 +355,7 @@ class BookmarkFileService implements BookmarkServiceInterface
365 /** 355 /**
366 * @inheritDoc 356 * @inheritDoc
367 */ 357 */
368 public function filterDay($request) 358 public function filterDay(string $request)
369 { 359 {
370 $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC; 360 $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
371 361
@@ -375,7 +365,7 @@ class BookmarkFileService implements BookmarkServiceInterface
375 /** 365 /**
376 * @inheritDoc 366 * @inheritDoc
377 */ 367 */
378 public function initialize() 368 public function initialize(): void
379 { 369 {
380 $initializer = new BookmarkInitializer($this); 370 $initializer = new BookmarkInitializer($this);
381 $initializer->initialize(); 371 $initializer->initialize();
@@ -388,7 +378,7 @@ class BookmarkFileService implements BookmarkServiceInterface
388 /** 378 /**
389 * Handles migration to the new database format (BookmarksArray). 379 * Handles migration to the new database format (BookmarksArray).
390 */ 380 */
391 protected function migrate() 381 protected function migrate(): void
392 { 382 {
393 $bookmarkDb = new LegacyLinkDB( 383 $bookmarkDb = new LegacyLinkDB(
394 $this->conf->get('resource.datastore'), 384 $this->conf->get('resource.datastore'),
diff --git a/application/bookmark/BookmarkFilter.php b/application/bookmark/BookmarkFilter.php
index 6636bbfe..4232f114 100644
--- a/application/bookmark/BookmarkFilter.php
+++ b/application/bookmark/BookmarkFilter.php
@@ -1,5 +1,7 @@
1<?php 1<?php
2 2
3declare(strict_types=1);
4
3namespace Shaarli\Bookmark; 5namespace Shaarli\Bookmark;
4 6
5use Exception; 7use Exception;
@@ -77,8 +79,13 @@ class BookmarkFilter
77 * 79 *
78 * @throws BookmarkNotFoundException 80 * @throws BookmarkNotFoundException
79 */ 81 */
80 public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false) 82 public function filter(
81 { 83 string $type,
84 $request,
85 bool $casesensitive = false,
86 string $visibility = 'all',
87 bool $untaggedonly = false
88 ) {
82 if (!in_array($visibility, ['all', 'public', 'private'])) { 89 if (!in_array($visibility, ['all', 'public', 'private'])) {
83 $visibility = 'all'; 90 $visibility = 'all';
84 } 91 }
@@ -128,7 +135,7 @@ class BookmarkFilter
128 * 135 *
129 * @return Bookmark[] filtered bookmarks. 136 * @return Bookmark[] filtered bookmarks.
130 */ 137 */
131 private function noFilter($visibility = 'all') 138 private function noFilter(string $visibility = 'all')
132 { 139 {
133 if ($visibility === 'all') { 140 if ($visibility === 'all') {
134 return $this->bookmarks; 141 return $this->bookmarks;
@@ -151,11 +158,11 @@ class BookmarkFilter
151 * 158 *
152 * @param string $smallHash permalink hash. 159 * @param string $smallHash permalink hash.
153 * 160 *
154 * @return array $filtered array containing permalink data. 161 * @return Bookmark[] $filtered array containing permalink data.
155 * 162 *
156 * @throws \Shaarli\Bookmark\Exception\BookmarkNotFoundException if the smallhash doesn't match any link. 163 * @throws BookmarkNotFoundException if the smallhash doesn't match any link.
157 */ 164 */
158 private function filterSmallHash($smallHash) 165 private function filterSmallHash(string $smallHash)
159 { 166 {
160 foreach ($this->bookmarks as $key => $l) { 167 foreach ($this->bookmarks as $key => $l) {
161 if ($smallHash == $l->getShortUrl()) { 168 if ($smallHash == $l->getShortUrl()) {
@@ -186,9 +193,9 @@ class BookmarkFilter
186 * @param string $searchterms search query. 193 * @param string $searchterms search query.
187 * @param string $visibility Optional: return only all/private/public bookmarks. 194 * @param string $visibility Optional: return only all/private/public bookmarks.
188 * 195 *
189 * @return array search results. 196 * @return Bookmark[] search results.
190 */ 197 */
191 private function filterFulltext($searchterms, $visibility = 'all') 198 private function filterFulltext(string $searchterms, string $visibility = 'all')
192 { 199 {
193 if (empty($searchterms)) { 200 if (empty($searchterms)) {
194 return $this->noFilter($visibility); 201 return $this->noFilter($visibility);
@@ -268,7 +275,7 @@ class BookmarkFilter
268 * 275 *
269 * @return string generated regex fragment 276 * @return string generated regex fragment
270 */ 277 */
271 private static function tag2regex($tag) 278 private static function tag2regex(string $tag): string
272 { 279 {
273 $len = strlen($tag); 280 $len = strlen($tag);
274 if (!$len || $tag === "-" || $tag === "*") { 281 if (!$len || $tag === "-" || $tag === "*") {
@@ -314,13 +321,13 @@ class BookmarkFilter
314 * You can specify one or more tags, separated by space or a comma, e.g. 321 * You can specify one or more tags, separated by space or a comma, e.g.
315 * print_r($mydb->filterTags('linux programming')); 322 * print_r($mydb->filterTags('linux programming'));
316 * 323 *
317 * @param string $tags list of tags separated by commas or blank spaces. 324 * @param string|array $tags list of tags, separated by commas or blank spaces if passed as string.
318 * @param bool $casesensitive ignore case if false. 325 * @param bool $casesensitive ignore case if false.
319 * @param string $visibility Optional: return only all/private/public bookmarks. 326 * @param string $visibility Optional: return only all/private/public bookmarks.
320 * 327 *
321 * @return array filtered bookmarks. 328 * @return Bookmark[] filtered bookmarks.
322 */ 329 */
323 public function filterTags($tags, $casesensitive = false, $visibility = 'all') 330 public function filterTags($tags, bool $casesensitive = false, string $visibility = 'all')
324 { 331 {
325 // get single tags (we may get passed an array, even though the docs say different) 332 // get single tags (we may get passed an array, even though the docs say different)
326 $inputTags = $tags; 333 $inputTags = $tags;
@@ -396,9 +403,9 @@ class BookmarkFilter
396 * 403 *
397 * @param string $visibility return only all/private/public bookmarks. 404 * @param string $visibility return only all/private/public bookmarks.
398 * 405 *
399 * @return array filtered bookmarks. 406 * @return Bookmark[] filtered bookmarks.
400 */ 407 */
401 public function filterUntagged($visibility) 408 public function filterUntagged(string $visibility)
402 { 409 {
403 $filtered = []; 410 $filtered = [];
404 foreach ($this->bookmarks as $key => $link) { 411 foreach ($this->bookmarks as $key => $link) {
@@ -427,11 +434,11 @@ class BookmarkFilter
427 * @param string $day day to filter. 434 * @param string $day day to filter.
428 * @param string $visibility return only all/private/public bookmarks. 435 * @param string $visibility return only all/private/public bookmarks.
429 436
430 * @return array all link matching given day. 437 * @return Bookmark[] all link matching given day.
431 * 438 *
432 * @throws Exception if date format is invalid. 439 * @throws Exception if date format is invalid.
433 */ 440 */
434 public function filterDay($day, $visibility) 441 public function filterDay(string $day, string $visibility)
435 { 442 {
436 if (!checkDateFormat('Ymd', $day)) { 443 if (!checkDateFormat('Ymd', $day)) {
437 throw new Exception('Invalid date format'); 444 throw new Exception('Invalid date format');
@@ -460,9 +467,9 @@ class BookmarkFilter
460 * @param string $tags string containing a list of tags. 467 * @param string $tags string containing a list of tags.
461 * @param bool $casesensitive will convert everything to lowercase if false. 468 * @param bool $casesensitive will convert everything to lowercase if false.
462 * 469 *
463 * @return array filtered tags string. 470 * @return string[] filtered tags string.
464 */ 471 */
465 public static function tagsStrToArray($tags, $casesensitive) 472 public static function tagsStrToArray(string $tags, bool $casesensitive): array
466 { 473 {
467 // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek) 474 // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
468 $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8'); 475 $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8');
diff --git a/application/bookmark/BookmarkIO.php b/application/bookmark/BookmarkIO.php
index 099653b0..f40fa476 100644
--- a/application/bookmark/BookmarkIO.php
+++ b/application/bookmark/BookmarkIO.php
@@ -1,5 +1,7 @@
1<?php 1<?php
2 2
3declare(strict_types=1);
4
3namespace Shaarli\Bookmark; 5namespace Shaarli\Bookmark;
4 6
5use malkusch\lock\mutex\Mutex; 7use malkusch\lock\mutex\Mutex;
@@ -61,7 +63,7 @@ class BookmarkIO
61 /** 63 /**
62 * Reads database from disk to memory 64 * Reads database from disk to memory
63 * 65 *
64 * @return BookmarkArray instance 66 * @return Bookmark[]
65 * 67 *
66 * @throws NotWritableDataStoreException Data couldn't be loaded 68 * @throws NotWritableDataStoreException Data couldn't be loaded
67 * @throws EmptyDataStoreException Datastore file exists but does not contain any bookmark 69 * @throws EmptyDataStoreException Datastore file exists but does not contain any bookmark
@@ -101,7 +103,7 @@ class BookmarkIO
101 /** 103 /**
102 * Saves the database from memory to disk 104 * Saves the database from memory to disk
103 * 105 *
104 * @param BookmarkArray $links instance. 106 * @param Bookmark[] $links
105 * 107 *
106 * @throws NotWritableDataStoreException the datastore is not writable 108 * @throws NotWritableDataStoreException the datastore is not writable
107 */ 109 */
diff --git a/application/bookmark/BookmarkInitializer.php b/application/bookmark/BookmarkInitializer.php
index 815047e3..04b996f3 100644
--- a/application/bookmark/BookmarkInitializer.php
+++ b/application/bookmark/BookmarkInitializer.php
@@ -1,5 +1,7 @@
1<?php 1<?php
2 2
3declare(strict_types=1);
4
3namespace Shaarli\Bookmark; 5namespace Shaarli\Bookmark;
4 6
5/** 7/**
@@ -23,7 +25,7 @@ class BookmarkInitializer
23 * 25 *
24 * @param BookmarkServiceInterface $bookmarkService 26 * @param BookmarkServiceInterface $bookmarkService
25 */ 27 */
26 public function __construct($bookmarkService) 28 public function __construct(BookmarkServiceInterface $bookmarkService)
27 { 29 {
28 $this->bookmarkService = $bookmarkService; 30 $this->bookmarkService = $bookmarkService;
29 } 31 }
@@ -31,7 +33,7 @@ class BookmarkInitializer
31 /** 33 /**
32 * Initialize the data store with default bookmarks 34 * Initialize the data store with default bookmarks
33 */ 35 */
34 public function initialize() 36 public function initialize(): void
35 { 37 {
36 $bookmark = new Bookmark(); 38 $bookmark = new Bookmark();
37 $bookmark->setTitle('quicksilver (loop) on Vimeo ' . t('(private bookmark with thumbnail demo)')); 39 $bookmark->setTitle('quicksilver (loop) on Vimeo ' . t('(private bookmark with thumbnail demo)'));
diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php
index 638cfa5f..37a54d03 100644
--- a/application/bookmark/BookmarkServiceInterface.php
+++ b/application/bookmark/BookmarkServiceInterface.php
@@ -1,7 +1,8 @@
1<?php 1<?php
2 2
3namespace Shaarli\Bookmark; 3declare(strict_types=1);
4 4
5namespace Shaarli\Bookmark;
5 6
6use Shaarli\Bookmark\Exception\BookmarkNotFoundException; 7use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
7use Shaarli\Bookmark\Exception\NotWritableDataStoreException; 8use Shaarli\Bookmark\Exception\NotWritableDataStoreException;
@@ -10,6 +11,9 @@ use Shaarli\Bookmark\Exception\NotWritableDataStoreException;
10 * Class BookmarksService 11 * Class BookmarksService
11 * 12 *
12 * This is the entry point to manipulate the bookmark DB. 13 * This is the entry point to manipulate the bookmark DB.
14 *
15 * Regarding return types of a list of bookmarks, it can either be an array or an ArrayAccess implementation,
16 * so until PHP 8.0 is the minimal supported version with union return types it cannot be explicitly added.
13 */ 17 */
14interface BookmarkServiceInterface 18interface BookmarkServiceInterface
15{ 19{
@@ -18,51 +22,51 @@ interface BookmarkServiceInterface
18 * 22 *
19 * @param string $hash 23 * @param string $hash
20 * 24 *
21 * @return mixed 25 * @return Bookmark
22 * 26 *
23 * @throws \Exception 27 * @throws \Exception
24 */ 28 */
25 public function findByHash($hash); 29 public function findByHash(string $hash): Bookmark;
26 30
27 /** 31 /**
28 * @param $url 32 * @param $url
29 * 33 *
30 * @return Bookmark|null 34 * @return Bookmark|null
31 */ 35 */
32 public function findByUrl($url); 36 public function findByUrl(string $url): ?Bookmark;
33 37
34 /** 38 /**
35 * Search bookmarks 39 * Search bookmarks
36 * 40 *
37 * @param mixed $request 41 * @param array $request
38 * @param string $visibility 42 * @param ?string $visibility
39 * @param bool $caseSensitive 43 * @param bool $caseSensitive
40 * @param bool $untaggedOnly 44 * @param bool $untaggedOnly
41 * @param bool $ignoreSticky 45 * @param bool $ignoreSticky
42 * 46 *
43 * @return Bookmark[] 47 * @return Bookmark[]
44 */ 48 */
45 public function search( 49 public function search(
46 $request = [], 50 array $request = [],
47 $visibility = null, 51 string $visibility = null,
48 $caseSensitive = false, 52 bool $caseSensitive = false,
49 $untaggedOnly = false, 53 bool $untaggedOnly = false,
50 bool $ignoreSticky = false 54 bool $ignoreSticky = false
51 ); 55 );
52 56
53 /** 57 /**
54 * Get a single bookmark by its ID. 58 * Get a single bookmark by its ID.
55 * 59 *
56 * @param int $id Bookmark ID 60 * @param int $id Bookmark ID
57 * @param string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an 61 * @param ?string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an
58 * exception 62 * exception
59 * 63 *
60 * @return Bookmark 64 * @return Bookmark
61 * 65 *
62 * @throws BookmarkNotFoundException 66 * @throws BookmarkNotFoundException
63 * @throws \Exception 67 * @throws \Exception
64 */ 68 */
65 public function get($id, $visibility = null); 69 public function get(int $id, string $visibility = null);
66 70
67 /** 71 /**
68 * Updates an existing bookmark (depending on its ID). 72 * Updates an existing bookmark (depending on its ID).
@@ -75,7 +79,7 @@ interface BookmarkServiceInterface
75 * @throws BookmarkNotFoundException 79 * @throws BookmarkNotFoundException
76 * @throws \Exception 80 * @throws \Exception
77 */ 81 */
78 public function set($bookmark, $save = true); 82 public function set(Bookmark $bookmark, bool $save = true): Bookmark;
79 83
80 /** 84 /**
81 * Adds a new bookmark (the ID must be empty). 85 * Adds a new bookmark (the ID must be empty).
@@ -87,7 +91,7 @@ interface BookmarkServiceInterface
87 * 91 *
88 * @throws \Exception 92 * @throws \Exception
89 */ 93 */
90 public function add($bookmark, $save = true); 94 public function add(Bookmark $bookmark, bool $save = true): Bookmark;
91 95
92 /** 96 /**
93 * Adds or updates a bookmark depending on its ID: 97 * Adds or updates a bookmark depending on its ID:
@@ -101,7 +105,7 @@ interface BookmarkServiceInterface
101 * 105 *
102 * @throws \Exception 106 * @throws \Exception
103 */ 107 */
104 public function addOrSet($bookmark, $save = true); 108 public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark;
105 109
106 /** 110 /**
107 * Deletes a bookmark. 111 * Deletes a bookmark.
@@ -111,51 +115,51 @@ interface BookmarkServiceInterface
111 * 115 *
112 * @throws \Exception 116 * @throws \Exception
113 */ 117 */
114 public function remove($bookmark, $save = true); 118 public function remove(Bookmark $bookmark, bool $save = true): void;
115 119
116 /** 120 /**
117 * Get a single bookmark by its ID. 121 * Get a single bookmark by its ID.
118 * 122 *
119 * @param int $id Bookmark ID 123 * @param int $id Bookmark ID
120 * @param string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an 124 * @param ?string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an
121 * exception 125 * exception
122 * 126 *
123 * @return bool 127 * @return bool
124 */ 128 */
125 public function exists($id, $visibility = null); 129 public function exists(int $id, string $visibility = null): bool;
126 130
127 /** 131 /**
128 * Return the number of available bookmarks for given visibility. 132 * Return the number of available bookmarks for given visibility.
129 * 133 *
130 * @param string $visibility public|private|all 134 * @param ?string $visibility public|private|all
131 * 135 *
132 * @return int Number of bookmarks 136 * @return int Number of bookmarks
133 */ 137 */
134 public function count($visibility = null); 138 public function count(string $visibility = null): int;
135 139
136 /** 140 /**
137 * Write the datastore. 141 * Write the datastore.
138 * 142 *
139 * @throws NotWritableDataStoreException 143 * @throws NotWritableDataStoreException
140 */ 144 */
141 public function save(); 145 public function save(): void;
142 146
143 /** 147 /**
144 * Returns the list tags appearing in the bookmarks with the given tags 148 * Returns the list tags appearing in the bookmarks with the given tags
145 * 149 *
146 * @param array $filteringTags tags selecting the bookmarks to consider 150 * @param array|null $filteringTags tags selecting the bookmarks to consider
147 * @param string $visibility process only all/private/public bookmarks 151 * @param string|null $visibility process only all/private/public bookmarks
148 * 152 *
149 * @return array tag => bookmarksCount 153 * @return array tag => bookmarksCount
150 */ 154 */
151 public function bookmarksCountPerTag($filteringTags = [], $visibility = 'all'); 155 public function bookmarksCountPerTag(array $filteringTags = [], ?string $visibility = null): array;
152 156
153 /** 157 /**
154 * Returns the list of days containing articles (oldest first) 158 * Returns the list of days containing articles (oldest first)
155 * 159 *
156 * @return array containing days (in format YYYYMMDD). 160 * @return array containing days (in format YYYYMMDD).
157 */ 161 */
158 public function days(); 162 public function days(): array;
159 163
160 /** 164 /**
161 * Returns the list of articles for a given day. 165 * Returns the list of articles for a given day.
@@ -166,10 +170,10 @@ interface BookmarkServiceInterface
166 * 170 *
167 * @throws BookmarkNotFoundException 171 * @throws BookmarkNotFoundException
168 */ 172 */
169 public function filterDay($request); 173 public function filterDay(string $request);
170 174
171 /** 175 /**
172 * Creates the default database after a fresh install. 176 * Creates the default database after a fresh install.
173 */ 177 */
174 public function initialize(); 178 public function initialize(): void;
175} 179}
diff --git a/application/feed/FeedBuilder.php b/application/feed/FeedBuilder.php
index f6def630..f70fce4f 100644
--- a/application/feed/FeedBuilder.php
+++ b/application/feed/FeedBuilder.php
@@ -102,7 +102,7 @@ class FeedBuilder
102 } 102 }
103 103
104 // Optionally filter the results: 104 // Optionally filter the results:
105 $linksToDisplay = $this->linkDB->search($userInput, null, false, false, true); 105 $linksToDisplay = $this->linkDB->search($userInput ?? [], null, false, false, true);
106 106
107 $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay), $userInput); 107 $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay), $userInput);
108 108
diff --git a/application/front/controller/admin/ThumbnailsController.php b/application/front/controller/admin/ThumbnailsController.php
index 81c87ed0..4dc09d38 100644
--- a/application/front/controller/admin/ThumbnailsController.php
+++ b/application/front/controller/admin/ThumbnailsController.php
@@ -52,7 +52,7 @@ class ThumbnailsController extends ShaarliAdminController
52 } 52 }
53 53
54 try { 54 try {
55 $bookmark = $this->container->bookmarkService->get($id); 55 $bookmark = $this->container->bookmarkService->get((int) $id);
56 } catch (BookmarkNotFoundException $e) { 56 } catch (BookmarkNotFoundException $e) {
57 return $response->withStatus(404); 57 return $response->withStatus(404);
58 } 58 }
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php
index d74c3118..65048f10 100644
--- a/application/security/LoginManager.php
+++ b/application/security/LoginManager.php
@@ -118,7 +118,7 @@ class LoginManager
118 * 118 *
119 * @return true when the user is logged in, false otherwise 119 * @return true when the user is logged in, false otherwise
120 */ 120 */
121 public function isLoggedIn() 121 public function isLoggedIn(): bool
122 { 122 {
123 if ($this->openShaarli) { 123 if ($this->openShaarli) {
124 return true; 124 return true;
diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php
index 6dc0e5b7..e810104e 100644
--- a/tests/HistoryTest.php
+++ b/tests/HistoryTest.php
@@ -89,14 +89,6 @@ class HistoryTest extends \Shaarli\TestCase
89 $this->assertEquals(History::CREATED, $actual['event']); 89 $this->assertEquals(History::CREATED, $actual['event']);
90 $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); 90 $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
91 $this->assertEquals(1, $actual['id']); 91 $this->assertEquals(1, $actual['id']);
92
93 $history = new History(self::$historyFilePath);
94 $bookmark = (new Bookmark())->setId('str');
95 $history->addLink($bookmark);
96 $actual = $history->getHistory()[0];
97 $this->assertEquals(History::CREATED, $actual['event']);
98 $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
99 $this->assertEquals('str', $actual['id']);
100 } 92 }
101 93
102// /** 94// /**
diff --git a/tests/bookmark/BookmarkArrayTest.php b/tests/bookmark/BookmarkArrayTest.php
index ebed9bfc..1953078c 100644
--- a/tests/bookmark/BookmarkArrayTest.php
+++ b/tests/bookmark/BookmarkArrayTest.php
@@ -91,19 +91,6 @@ class BookmarkArrayTest extends TestCase
91 } 91 }
92 92
93 /** 93 /**
94 * Test adding a bad entry: invalid ID type
95 */
96 public function testArrayAccessAddBadEntryIdType()
97 {
98 $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class);
99
100 $array = new BookmarkArray();
101 $bookmark = (new Bookmark())->setId('nope');
102 $bookmark->validate();
103 $array[] = $bookmark;
104 }
105
106 /**
107 * Test adding a bad entry: ID/offset not consistent 94 * Test adding a bad entry: ID/offset not consistent
108 */ 95 */
109 public function testArrayAccessAddBadEntryIdOffset() 96 public function testArrayAccessAddBadEntryIdOffset()
diff --git a/tests/bookmark/BookmarkFileServiceTest.php b/tests/bookmark/BookmarkFileServiceTest.php
index 6c56dfaa..59c0608c 100644
--- a/tests/bookmark/BookmarkFileServiceTest.php
+++ b/tests/bookmark/BookmarkFileServiceTest.php
@@ -265,17 +265,6 @@ class BookmarkFileServiceTest extends TestCase
265 } 265 }
266 266
267 /** 267 /**
268 * Test add() method with an entry which is not a bookmark instance
269 */
270 public function testAddNotABookmark()
271 {
272 $this->expectException(\Exception::class);
273 $this->expectExceptionMessage('Provided data is invalid');
274
275 $this->privateLinkDB->add(['title' => 'hi!']);
276 }
277
278 /**
279 * Test add() method with a Bookmark already containing an ID 268 * Test add() method with a Bookmark already containing an ID
280 */ 269 */
281 public function testAddWithId() 270 public function testAddWithId()
@@ -413,17 +402,6 @@ class BookmarkFileServiceTest extends TestCase
413 } 402 }
414 403
415 /** 404 /**
416 * Test set() method with an entry which is not a bookmark instance
417 */
418 public function testSetNotABookmark()
419 {
420 $this->expectException(\Exception::class);
421 $this->expectExceptionMessage('Provided data is invalid');
422
423 $this->privateLinkDB->set(['title' => 'hi!']);
424 }
425
426 /**
427 * Test set() method with a Bookmark without an ID defined. 405 * Test set() method with a Bookmark without an ID defined.
428 */ 406 */
429 public function testSetWithoutId() 407 public function testSetWithoutId()
@@ -497,17 +475,6 @@ class BookmarkFileServiceTest extends TestCase
497 } 475 }
498 476
499 /** 477 /**
500 * Test addOrSet() method with an entry which is not a bookmark instance
501 */
502 public function testAddOrSetNotABookmark()
503 {
504 $this->expectException(\Exception::class);
505 $this->expectExceptionMessage('Provided data is invalid');
506
507 $this->privateLinkDB->addOrSet(['title' => 'hi!']);
508 }
509
510 /**
511 * Test addOrSet() method for a bookmark without any field set and without writing the data store 478 * Test addOrSet() method for a bookmark without any field set and without writing the data store
512 */ 479 */
513 public function testAddOrSetMinimalNoWrite() 480 public function testAddOrSetMinimalNoWrite()
@@ -565,17 +532,6 @@ class BookmarkFileServiceTest extends TestCase
565 } 532 }
566 533
567 /** 534 /**
568 * Test remove() method with an entry which is not a bookmark instance
569 */
570 public function testRemoveNotABookmark()
571 {
572 $this->expectException(\Exception::class);
573 $this->expectExceptionMessage('Provided data is invalid');
574
575 $this->privateLinkDB->remove(['title' => 'hi!']);
576 }
577
578 /**
579 * Test remove() method with a Bookmark with an unknown ID 535 * Test remove() method with a Bookmark with an unknown ID
580 */ 536 */
581 public function testRemoveWithUnknownId() 537 public function testRemoveWithUnknownId()
diff --git a/tests/bookmark/BookmarkTest.php b/tests/bookmark/BookmarkTest.php
index afec2440..4c7ae4c0 100644
--- a/tests/bookmark/BookmarkTest.php
+++ b/tests/bookmark/BookmarkTest.php
@@ -154,25 +154,6 @@ class BookmarkTest extends TestCase
154 } 154 }
155 155
156 /** 156 /**
157 * Test validate() with a a bookmark with a non integer ID.
158 */
159 public function testValidateNotValidStringId()
160 {
161 $bookmark = new Bookmark();
162 $bookmark->setId('str');
163 $bookmark->setShortUrl('abc');
164 $bookmark->setCreated(\DateTime::createFromFormat('Ymd_His', '20190514_200102'));
165 $exception = null;
166 try {
167 $bookmark->validate();
168 } catch (InvalidBookmarkException $e) {
169 $exception = $e;
170 }
171 $this->assertNotNull($exception);
172 $this->assertContainsPolyfill('- ID: str'. PHP_EOL, $exception->getMessage());
173 }
174
175 /**
176 * Test validate() with a a bookmark without short url. 157 * Test validate() with a a bookmark without short url.
177 */ 158 */
178 public function testValidateNotValidNoShortUrl() 159 public function testValidateNotValidNoShortUrl()
@@ -211,25 +192,6 @@ class BookmarkTest extends TestCase
211 } 192 }
212 193
213 /** 194 /**
214 * Test validate() with a a bookmark with a bad created datetime.
215 */
216 public function testValidateNotValidBadCreated()
217 {
218 $bookmark = new Bookmark();
219 $bookmark->setId(1);
220 $bookmark->setShortUrl('abc');
221 $bookmark->setCreated('hi!');
222 $exception = null;
223 try {
224 $bookmark->validate();
225 } catch (InvalidBookmarkException $e) {
226 $exception = $e;
227 }
228 $this->assertNotNull($exception);
229 $this->assertContainsPolyfill('- Created: Not a DateTime object'. PHP_EOL, $exception->getMessage());
230 }
231
232 /**
233 * Test setId() and make sure that default fields are generated. 195 * Test setId() and make sure that default fields are generated.
234 */ 196 */
235 public function testSetIdEmptyGeneratedFields() 197 public function testSetIdEmptyGeneratedFields()
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php b/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php
index ba774e21..83bbee7c 100644
--- a/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php
+++ b/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php
@@ -356,6 +356,10 @@ class DeleteBookmarkTest extends TestCase
356 ; 356 ;
357 $response = new Response(); 357 $response = new Response();
358 358
359 $this->container->bookmarkService->method('get')->with('123')->willReturn(
360 (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')
361 );
362
359 $this->container->formatterFactory = $this->createMock(FormatterFactory::class); 363 $this->container->formatterFactory = $this->createMock(FormatterFactory::class);
360 $this->container->formatterFactory 364 $this->container->formatterFactory
361 ->expects(static::once()) 365 ->expects(static::once())
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php b/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php
index f7a68226..37542c26 100644
--- a/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php
+++ b/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php
@@ -66,23 +66,27 @@ class SaveBookmarkTest extends TestCase
66 $this->container->bookmarkService 66 $this->container->bookmarkService
67 ->expects(static::once()) 67 ->expects(static::once())
68 ->method('addOrSet') 68 ->method('addOrSet')
69 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { 69 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
70 static::assertFalse($save); 70 static::assertFalse($save);
71 71
72 $checkBookmark($bookmark); 72 $checkBookmark($bookmark);
73 73
74 $bookmark->setId($id); 74 $bookmark->setId($id);
75
76 return $bookmark;
75 }) 77 })
76 ; 78 ;
77 $this->container->bookmarkService 79 $this->container->bookmarkService
78 ->expects(static::once()) 80 ->expects(static::once())
79 ->method('set') 81 ->method('set')
80 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { 82 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
81 static::assertTrue($save); 83 static::assertTrue($save);
82 84
83 $checkBookmark($bookmark); 85 $checkBookmark($bookmark);
84 86
85 static::assertSame($id, $bookmark->getId()); 87 static::assertSame($id, $bookmark->getId());
88
89 return $bookmark;
86 }) 90 })
87 ; 91 ;
88 92
@@ -155,21 +159,25 @@ class SaveBookmarkTest extends TestCase
155 $this->container->bookmarkService 159 $this->container->bookmarkService
156 ->expects(static::once()) 160 ->expects(static::once())
157 ->method('addOrSet') 161 ->method('addOrSet')
158 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { 162 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
159 static::assertFalse($save); 163 static::assertFalse($save);
160 164
161 $checkBookmark($bookmark); 165 $checkBookmark($bookmark);
166
167 return $bookmark;
162 }) 168 })
163 ; 169 ;
164 $this->container->bookmarkService 170 $this->container->bookmarkService
165 ->expects(static::once()) 171 ->expects(static::once())
166 ->method('set') 172 ->method('set')
167 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { 173 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
168 static::assertTrue($save); 174 static::assertTrue($save);
169 175
170 $checkBookmark($bookmark); 176 $checkBookmark($bookmark);
171 177
172 static::assertSame($id, $bookmark->getId()); 178 static::assertSame($id, $bookmark->getId());
179
180 return $bookmark;
173 }) 181 })
174 ; 182 ;
175 183
@@ -230,8 +238,10 @@ class SaveBookmarkTest extends TestCase
230 $this->container->bookmarkService 238 $this->container->bookmarkService
231 ->expects(static::once()) 239 ->expects(static::once())
232 ->method('addOrSet') 240 ->method('addOrSet')
233 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($thumb): void { 241 ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($thumb): Bookmark {
234 static::assertSame($thumb, $bookmark->getThumbnail()); 242 static::assertSame($thumb, $bookmark->getThumbnail());
243
244 return $bookmark;
235 }) 245 })
236 ; 246 ;
237 247
diff --git a/tests/front/controller/admin/ThumbnailsControllerTest.php b/tests/front/controller/admin/ThumbnailsControllerTest.php
index f4a8acff..e5749654 100644
--- a/tests/front/controller/admin/ThumbnailsControllerTest.php
+++ b/tests/front/controller/admin/ThumbnailsControllerTest.php
@@ -89,8 +89,10 @@ class ThumbnailsControllerTest extends TestCase
89 $this->container->bookmarkService 89 $this->container->bookmarkService
90 ->expects(static::once()) 90 ->expects(static::once())
91 ->method('set') 91 ->method('set')
92 ->willReturnCallback(function (Bookmark $bookmark) use ($thumb) { 92 ->willReturnCallback(function (Bookmark $bookmark) use ($thumb): Bookmark {
93 static::assertSame($thumb, $bookmark->getThumbnail()); 93 static::assertSame($thumb, $bookmark->getThumbnail());
94
95 return $bookmark;
94 }) 96 })
95 ; 97 ;
96 98