diff options
Diffstat (limited to 'application/bookmark/Bookmark.php')
-rw-r--r-- | application/bookmark/Bookmark.php | 217 |
1 files changed, 143 insertions, 74 deletions
diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php index 1beb8be2..4238ef25 100644 --- a/application/bookmark/Bookmark.php +++ b/application/bookmark/Bookmark.php | |||
@@ -1,5 +1,7 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | declare(strict_types=1); | ||
4 | |||
3 | namespace Shaarli\Bookmark; | 5 | namespace Shaarli\Bookmark; |
4 | 6 | ||
5 | use DateTime; | 7 | use DateTime; |
@@ -17,7 +19,7 @@ use Shaarli\Bookmark\Exception\InvalidBookmarkException; | |||
17 | class Bookmark | 19 | class Bookmark |
18 | { | 20 | { |
19 | /** @var string Date format used in string (former ID format) */ | 21 | /** @var string Date format used in string (former ID format) */ |
20 | const LINK_DATE_FORMAT = 'Ymd_His'; | 22 | public const LINK_DATE_FORMAT = 'Ymd_His'; |
21 | 23 | ||
22 | /** @var int Bookmark ID */ | 24 | /** @var int Bookmark ID */ |
23 | protected $id; | 25 | protected $id; |
@@ -52,32 +54,37 @@ class Bookmark | |||
52 | /** @var bool True if the bookmark can only be seen while logged in */ | 54 | /** @var bool True if the bookmark can only be seen while logged in */ |
53 | protected $private; | 55 | protected $private; |
54 | 56 | ||
57 | /** @var mixed[] Available to store any additional content for a bookmark. Currently used for search highlight. */ | ||
58 | protected $additionalContent = []; | ||
59 | |||
55 | /** | 60 | /** |
56 | * Initialize a link from array data. Especially useful to create a Bookmark from former link storage format. | 61 | * Initialize a link from array data. Especially useful to create a Bookmark from former link storage format. |
57 | * | 62 | * |
58 | * @param array $data | 63 | * @param array $data |
64 | * @param string $tagsSeparator Tags separator loaded from the config file. | ||
65 | * This is a context data, and it should *never* be stored in the Bookmark object. | ||
59 | * | 66 | * |
60 | * @return $this | 67 | * @return $this |
61 | */ | 68 | */ |
62 | public function fromArray($data) | 69 | public function fromArray(array $data, string $tagsSeparator = ' '): Bookmark |
63 | { | 70 | { |
64 | $this->id = $data['id']; | 71 | $this->id = $data['id'] ?? null; |
65 | $this->shortUrl = $data['shorturl']; | 72 | $this->shortUrl = $data['shorturl'] ?? null; |
66 | $this->url = $data['url']; | 73 | $this->url = $data['url'] ?? null; |
67 | $this->title = $data['title']; | 74 | $this->title = $data['title'] ?? null; |
68 | $this->description = $data['description']; | 75 | $this->description = $data['description'] ?? null; |
69 | $this->thumbnail = isset($data['thumbnail']) ? $data['thumbnail'] : null; | 76 | $this->thumbnail = $data['thumbnail'] ?? null; |
70 | $this->sticky = isset($data['sticky']) ? $data['sticky'] : false; | 77 | $this->sticky = $data['sticky'] ?? false; |
71 | $this->created = $data['created']; | 78 | $this->created = $data['created'] ?? null; |
72 | if (is_array($data['tags'])) { | 79 | if (is_array($data['tags'])) { |
73 | $this->tags = $data['tags']; | 80 | $this->tags = $data['tags']; |
74 | } else { | 81 | } else { |
75 | $this->tags = preg_split('/\s+/', $data['tags'], -1, PREG_SPLIT_NO_EMPTY); | 82 | $this->tags = tags_str2array($data['tags'] ?? '', $tagsSeparator); |
76 | } | 83 | } |
77 | if (! empty($data['updated'])) { | 84 | if (! empty($data['updated'])) { |
78 | $this->updated = $data['updated']; | 85 | $this->updated = $data['updated']; |
79 | } | 86 | } |
80 | $this->private = $data['private'] ? true : false; | 87 | $this->private = ($data['private'] ?? false) ? true : false; |
81 | 88 | ||
82 | return $this; | 89 | return $this; |
83 | } | 90 | } |
@@ -93,24 +100,29 @@ class Bookmark | |||
93 | * - the URL with the permalink | 100 | * - the URL with the permalink |
94 | * - the title with the URL | 101 | * - the title with the URL |
95 | * | 102 | * |
103 | * Also make sure that we do not save search highlights in the datastore. | ||
104 | * | ||
96 | * @throws InvalidBookmarkException | 105 | * @throws InvalidBookmarkException |
97 | */ | 106 | */ |
98 | public function validate() | 107 | public function validate(): void |
99 | { | 108 | { |
100 | if ($this->id === null | 109 | if ( |
110 | $this->id === null | ||
101 | || ! is_int($this->id) | 111 | || ! is_int($this->id) |
102 | || empty($this->shortUrl) | 112 | || empty($this->shortUrl) |
103 | || empty($this->created) | 113 | || empty($this->created) |
104 | || ! $this->created instanceof DateTimeInterface | ||
105 | ) { | 114 | ) { |
106 | throw new InvalidBookmarkException($this); | 115 | throw new InvalidBookmarkException($this); |
107 | } | 116 | } |
108 | if (empty($this->url)) { | 117 | if (empty($this->url)) { |
109 | $this->url = '/shaare/'. $this->shortUrl; | 118 | $this->url = '/shaare/' . $this->shortUrl; |
110 | } | 119 | } |
111 | if (empty($this->title)) { | 120 | if (empty($this->title)) { |
112 | $this->title = $this->url; | 121 | $this->title = $this->url; |
113 | } | 122 | } |
123 | if (array_key_exists('search_highlight', $this->additionalContent)) { | ||
124 | unset($this->additionalContent['search_highlight']); | ||
125 | } | ||
114 | } | 126 | } |
115 | 127 | ||
116 | /** | 128 | /** |
@@ -119,11 +131,11 @@ class Bookmark | |||
119 | * - created: with the current datetime | 131 | * - created: with the current datetime |
120 | * - shortUrl: with a generated small hash from the date and the given ID | 132 | * - shortUrl: with a generated small hash from the date and the given ID |
121 | * | 133 | * |
122 | * @param int $id | 134 | * @param int|null $id |
123 | * | 135 | * |
124 | * @return Bookmark | 136 | * @return Bookmark |
125 | */ | 137 | */ |
126 | public function setId($id) | 138 | public function setId(?int $id): Bookmark |
127 | { | 139 | { |
128 | $this->id = $id; | 140 | $this->id = $id; |
129 | if (empty($this->created)) { | 141 | if (empty($this->created)) { |
@@ -139,9 +151,9 @@ class Bookmark | |||
139 | /** | 151 | /** |
140 | * Get the Id. | 152 | * Get the Id. |
141 | * | 153 | * |
142 | * @return int | 154 | * @return int|null |
143 | */ | 155 | */ |
144 | public function getId() | 156 | public function getId(): ?int |
145 | { | 157 | { |
146 | return $this->id; | 158 | return $this->id; |
147 | } | 159 | } |
@@ -149,9 +161,9 @@ class Bookmark | |||
149 | /** | 161 | /** |
150 | * Get the ShortUrl. | 162 | * Get the ShortUrl. |
151 | * | 163 | * |
152 | * @return string | 164 | * @return string|null |
153 | */ | 165 | */ |
154 | public function getShortUrl() | 166 | public function getShortUrl(): ?string |
155 | { | 167 | { |
156 | return $this->shortUrl; | 168 | return $this->shortUrl; |
157 | } | 169 | } |
@@ -159,9 +171,9 @@ class Bookmark | |||
159 | /** | 171 | /** |
160 | * Get the Url. | 172 | * Get the Url. |
161 | * | 173 | * |
162 | * @return string | 174 | * @return string|null |
163 | */ | 175 | */ |
164 | public function getUrl() | 176 | public function getUrl(): ?string |
165 | { | 177 | { |
166 | return $this->url; | 178 | return $this->url; |
167 | } | 179 | } |
@@ -171,7 +183,7 @@ class Bookmark | |||
171 | * | 183 | * |
172 | * @return string | 184 | * @return string |
173 | */ | 185 | */ |
174 | public function getTitle() | 186 | public function getTitle(): ?string |
175 | { | 187 | { |
176 | return $this->title; | 188 | return $this->title; |
177 | } | 189 | } |
@@ -181,7 +193,7 @@ class Bookmark | |||
181 | * | 193 | * |
182 | * @return string | 194 | * @return string |
183 | */ | 195 | */ |
184 | public function getDescription() | 196 | public function getDescription(): string |
185 | { | 197 | { |
186 | return ! empty($this->description) ? $this->description : ''; | 198 | return ! empty($this->description) ? $this->description : ''; |
187 | } | 199 | } |
@@ -191,7 +203,7 @@ class Bookmark | |||
191 | * | 203 | * |
192 | * @return DateTimeInterface | 204 | * @return DateTimeInterface |
193 | */ | 205 | */ |
194 | public function getCreated() | 206 | public function getCreated(): ?DateTimeInterface |
195 | { | 207 | { |
196 | return $this->created; | 208 | return $this->created; |
197 | } | 209 | } |
@@ -201,7 +213,7 @@ class Bookmark | |||
201 | * | 213 | * |
202 | * @return DateTimeInterface | 214 | * @return DateTimeInterface |
203 | */ | 215 | */ |
204 | public function getUpdated() | 216 | public function getUpdated(): ?DateTimeInterface |
205 | { | 217 | { |
206 | return $this->updated; | 218 | return $this->updated; |
207 | } | 219 | } |
@@ -209,11 +221,11 @@ class Bookmark | |||
209 | /** | 221 | /** |
210 | * Set the ShortUrl. | 222 | * Set the ShortUrl. |
211 | * | 223 | * |
212 | * @param string $shortUrl | 224 | * @param string|null $shortUrl |
213 | * | 225 | * |
214 | * @return Bookmark | 226 | * @return Bookmark |
215 | */ | 227 | */ |
216 | public function setShortUrl($shortUrl) | 228 | public function setShortUrl(?string $shortUrl): Bookmark |
217 | { | 229 | { |
218 | $this->shortUrl = $shortUrl; | 230 | $this->shortUrl = $shortUrl; |
219 | 231 | ||
@@ -223,14 +235,14 @@ class Bookmark | |||
223 | /** | 235 | /** |
224 | * Set the Url. | 236 | * Set the Url. |
225 | * | 237 | * |
226 | * @param string $url | 238 | * @param string|null $url |
227 | * @param array $allowedProtocols | 239 | * @param string[] $allowedProtocols |
228 | * | 240 | * |
229 | * @return Bookmark | 241 | * @return Bookmark |
230 | */ | 242 | */ |
231 | public function setUrl($url, $allowedProtocols = []) | 243 | public function setUrl(?string $url, array $allowedProtocols = []): Bookmark |
232 | { | 244 | { |
233 | $url = trim($url); | 245 | $url = $url !== null ? trim($url) : ''; |
234 | if (! empty($url)) { | 246 | if (! empty($url)) { |
235 | $url = whitelist_protocols($url, $allowedProtocols); | 247 | $url = whitelist_protocols($url, $allowedProtocols); |
236 | } | 248 | } |
@@ -242,13 +254,13 @@ class Bookmark | |||
242 | /** | 254 | /** |
243 | * Set the Title. | 255 | * Set the Title. |
244 | * | 256 | * |
245 | * @param string $title | 257 | * @param string|null $title |
246 | * | 258 | * |
247 | * @return Bookmark | 259 | * @return Bookmark |
248 | */ | 260 | */ |
249 | public function setTitle($title) | 261 | public function setTitle(?string $title): Bookmark |
250 | { | 262 | { |
251 | $this->title = trim($title); | 263 | $this->title = $title !== null ? trim($title) : ''; |
252 | 264 | ||
253 | return $this; | 265 | return $this; |
254 | } | 266 | } |
@@ -256,11 +268,11 @@ class Bookmark | |||
256 | /** | 268 | /** |
257 | * Set the Description. | 269 | * Set the Description. |
258 | * | 270 | * |
259 | * @param string $description | 271 | * @param string|null $description |
260 | * | 272 | * |
261 | * @return Bookmark | 273 | * @return Bookmark |
262 | */ | 274 | */ |
263 | public function setDescription($description) | 275 | public function setDescription(?string $description): Bookmark |
264 | { | 276 | { |
265 | $this->description = $description; | 277 | $this->description = $description; |
266 | 278 | ||
@@ -271,11 +283,11 @@ class Bookmark | |||
271 | * Set the Created. | 283 | * Set the Created. |
272 | * Note: you shouldn't set this manually except for special cases (like bookmark import) | 284 | * Note: you shouldn't set this manually except for special cases (like bookmark import) |
273 | * | 285 | * |
274 | * @param DateTimeInterface $created | 286 | * @param DateTimeInterface|null $created |
275 | * | 287 | * |
276 | * @return Bookmark | 288 | * @return Bookmark |
277 | */ | 289 | */ |
278 | public function setCreated($created) | 290 | public function setCreated(?DateTimeInterface $created): Bookmark |
279 | { | 291 | { |
280 | $this->created = $created; | 292 | $this->created = $created; |
281 | 293 | ||
@@ -285,11 +297,11 @@ class Bookmark | |||
285 | /** | 297 | /** |
286 | * Set the Updated. | 298 | * Set the Updated. |
287 | * | 299 | * |
288 | * @param DateTimeInterface $updated | 300 | * @param DateTimeInterface|null $updated |
289 | * | 301 | * |
290 | * @return Bookmark | 302 | * @return Bookmark |
291 | */ | 303 | */ |
292 | public function setUpdated($updated) | 304 | public function setUpdated(?DateTimeInterface $updated): Bookmark |
293 | { | 305 | { |
294 | $this->updated = $updated; | 306 | $this->updated = $updated; |
295 | 307 | ||
@@ -301,7 +313,7 @@ class Bookmark | |||
301 | * | 313 | * |
302 | * @return bool | 314 | * @return bool |
303 | */ | 315 | */ |
304 | public function isPrivate() | 316 | public function isPrivate(): bool |
305 | { | 317 | { |
306 | return $this->private ? true : false; | 318 | return $this->private ? true : false; |
307 | } | 319 | } |
@@ -309,11 +321,11 @@ class Bookmark | |||
309 | /** | 321 | /** |
310 | * Set the Private. | 322 | * Set the Private. |
311 | * | 323 | * |
312 | * @param bool $private | 324 | * @param bool|null $private |
313 | * | 325 | * |
314 | * @return Bookmark | 326 | * @return Bookmark |
315 | */ | 327 | */ |
316 | public function setPrivate($private) | 328 | public function setPrivate(?bool $private): Bookmark |
317 | { | 329 | { |
318 | $this->private = $private ? true : false; | 330 | $this->private = $private ? true : false; |
319 | 331 | ||
@@ -323,9 +335,9 @@ class Bookmark | |||
323 | /** | 335 | /** |
324 | * Get the Tags. | 336 | * Get the Tags. |
325 | * | 337 | * |
326 | * @return array | 338 | * @return string[] |
327 | */ | 339 | */ |
328 | public function getTags() | 340 | public function getTags(): array |
329 | { | 341 | { |
330 | return is_array($this->tags) ? $this->tags : []; | 342 | return is_array($this->tags) ? $this->tags : []; |
331 | } | 343 | } |
@@ -333,13 +345,18 @@ class Bookmark | |||
333 | /** | 345 | /** |
334 | * Set the Tags. | 346 | * Set the Tags. |
335 | * | 347 | * |
336 | * @param array $tags | 348 | * @param string[]|null $tags |
337 | * | 349 | * |
338 | * @return Bookmark | 350 | * @return Bookmark |
339 | */ | 351 | */ |
340 | public function setTags($tags) | 352 | public function setTags(?array $tags): Bookmark |
341 | { | 353 | { |
342 | $this->setTagsString(implode(' ', $tags)); | 354 | $this->tags = array_map( |
355 | function (string $tag): string { | ||
356 | return $tag[0] === '-' ? substr($tag, 1) : $tag; | ||
357 | }, | ||
358 | tags_filter($tags, ' ') | ||
359 | ); | ||
343 | 360 | ||
344 | return $this; | 361 | return $this; |
345 | } | 362 | } |
@@ -357,11 +374,11 @@ class Bookmark | |||
357 | /** | 374 | /** |
358 | * Set the Thumbnail. | 375 | * Set the Thumbnail. |
359 | * | 376 | * |
360 | * @param string|bool $thumbnail Thumbnail's URL - false if no thumbnail could be found | 377 | * @param string|bool|null $thumbnail Thumbnail's URL - false if no thumbnail could be found |
361 | * | 378 | * |
362 | * @return Bookmark | 379 | * @return Bookmark |
363 | */ | 380 | */ |
364 | public function setThumbnail($thumbnail) | 381 | public function setThumbnail($thumbnail): Bookmark |
365 | { | 382 | { |
366 | $this->thumbnail = $thumbnail; | 383 | $this->thumbnail = $thumbnail; |
367 | 384 | ||
@@ -369,11 +386,29 @@ class Bookmark | |||
369 | } | 386 | } |
370 | 387 | ||
371 | /** | 388 | /** |
389 | * Return true if: | ||
390 | * - the bookmark's thumbnail is not already set to false (= not found) | ||
391 | * - it's not a note | ||
392 | * - it's an HTTP(S) link | ||
393 | * - the thumbnail has not yet be retrieved (null) or its associated cache file doesn't exist anymore | ||
394 | * | ||
395 | * @return bool True if the bookmark's thumbnail needs to be retrieved. | ||
396 | */ | ||
397 | public function shouldUpdateThumbnail(): bool | ||
398 | { | ||
399 | return $this->thumbnail !== false | ||
400 | && !$this->isNote() | ||
401 | && startsWith(strtolower($this->url), 'http') | ||
402 | && (null === $this->thumbnail || !is_file($this->thumbnail)) | ||
403 | ; | ||
404 | } | ||
405 | |||
406 | /** | ||
372 | * Get the Sticky. | 407 | * Get the Sticky. |
373 | * | 408 | * |
374 | * @return bool | 409 | * @return bool |
375 | */ | 410 | */ |
376 | public function isSticky() | 411 | public function isSticky(): bool |
377 | { | 412 | { |
378 | return $this->sticky ? true : false; | 413 | return $this->sticky ? true : false; |
379 | } | 414 | } |
@@ -381,11 +416,11 @@ class Bookmark | |||
381 | /** | 416 | /** |
382 | * Set the Sticky. | 417 | * Set the Sticky. |
383 | * | 418 | * |
384 | * @param bool $sticky | 419 | * @param bool|null $sticky |
385 | * | 420 | * |
386 | * @return Bookmark | 421 | * @return Bookmark |
387 | */ | 422 | */ |
388 | public function setSticky($sticky) | 423 | public function setSticky(?bool $sticky): Bookmark |
389 | { | 424 | { |
390 | $this->sticky = $sticky ? true : false; | 425 | $this->sticky = $sticky ? true : false; |
391 | 426 | ||
@@ -393,17 +428,19 @@ class Bookmark | |||
393 | } | 428 | } |
394 | 429 | ||
395 | /** | 430 | /** |
396 | * @return string Bookmark's tags as a string, separated by a space | 431 | * @param string $separator Tags separator loaded from the config file. |
432 | * | ||
433 | * @return string Bookmark's tags as a string, separated by a separator | ||
397 | */ | 434 | */ |
398 | public function getTagsString() | 435 | public function getTagsString(string $separator = ' '): string |
399 | { | 436 | { |
400 | return implode(' ', $this->getTags()); | 437 | return tags_array2str($this->getTags(), $separator); |
401 | } | 438 | } |
402 | 439 | ||
403 | /** | 440 | /** |
404 | * @return bool | 441 | * @return bool |
405 | */ | 442 | */ |
406 | public function isNote() | 443 | public function isNote(): bool |
407 | { | 444 | { |
408 | // We check empty value to get a valid result if the link has not been saved yet | 445 | // 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] === '?'; | 446 | return empty($this->url) || startsWith($this->url, '/shaare/') || $this->url[0] === '?'; |
@@ -416,33 +453,65 @@ class Bookmark | |||
416 | * - multiple spaces will be removed | 453 | * - multiple spaces will be removed |
417 | * - trailing dash in tags will be removed | 454 | * - trailing dash in tags will be removed |
418 | * | 455 | * |
419 | * @param string $tags | 456 | * @param string|null $tags |
457 | * @param string $separator Tags separator loaded from the config file. | ||
420 | * | 458 | * |
421 | * @return $this | 459 | * @return $this |
422 | */ | 460 | */ |
423 | public function setTagsString($tags) | 461 | public function setTagsString(?string $tags, string $separator = ' '): Bookmark |
424 | { | 462 | { |
425 | // Remove first '-' char in tags. | 463 | $this->setTags(tags_str2array($tags, $separator)); |
426 | $tags = preg_replace('/(^| )\-/', '$1', $tags); | ||
427 | // Explode all tags separted by spaces or commas | ||
428 | $tags = preg_split('/[\s,]+/', $tags); | ||
429 | // Remove eventual empty values | ||
430 | $tags = array_values(array_filter($tags)); | ||
431 | 464 | ||
432 | $this->tags = $tags; | 465 | return $this; |
466 | } | ||
467 | |||
468 | /** | ||
469 | * Get entire additionalContent array. | ||
470 | * | ||
471 | * @return mixed[] | ||
472 | */ | ||
473 | public function getAdditionalContent(): array | ||
474 | { | ||
475 | return $this->additionalContent; | ||
476 | } | ||
477 | |||
478 | /** | ||
479 | * Set a single entry in additionalContent, by key. | ||
480 | * | ||
481 | * @param string $key | ||
482 | * @param mixed|null $value Any type of value can be set. | ||
483 | * | ||
484 | * @return $this | ||
485 | */ | ||
486 | public function addAdditionalContentEntry(string $key, $value): self | ||
487 | { | ||
488 | $this->additionalContent[$key] = $value; | ||
433 | 489 | ||
434 | return $this; | 490 | return $this; |
435 | } | 491 | } |
436 | 492 | ||
437 | /** | 493 | /** |
494 | * Get a single entry in additionalContent, by key. | ||
495 | * | ||
496 | * @param string $key | ||
497 | * @param mixed|null $default | ||
498 | * | ||
499 | * @return mixed|null can be any type or even null. | ||
500 | */ | ||
501 | public function getAdditionalContentEntry(string $key, $default = null) | ||
502 | { | ||
503 | return array_key_exists($key, $this->additionalContent) ? $this->additionalContent[$key] : $default; | ||
504 | } | ||
505 | |||
506 | /** | ||
438 | * Rename a tag in tags list. | 507 | * Rename a tag in tags list. |
439 | * | 508 | * |
440 | * @param string $fromTag | 509 | * @param string $fromTag |
441 | * @param string $toTag | 510 | * @param string $toTag |
442 | */ | 511 | */ |
443 | public function renameTag($fromTag, $toTag) | 512 | public function renameTag(string $fromTag, string $toTag): void |
444 | { | 513 | { |
445 | if (($pos = array_search($fromTag, $this->tags)) !== false) { | 514 | if (($pos = array_search($fromTag, $this->tags ?? [])) !== false) { |
446 | $this->tags[$pos] = trim($toTag); | 515 | $this->tags[$pos] = trim($toTag); |
447 | } | 516 | } |
448 | } | 517 | } |
@@ -452,9 +521,9 @@ class Bookmark | |||
452 | * | 521 | * |
453 | * @param string $tag | 522 | * @param string $tag |
454 | */ | 523 | */ |
455 | public function deleteTag($tag) | 524 | public function deleteTag(string $tag): void |
456 | { | 525 | { |
457 | if (($pos = array_search($tag, $this->tags)) !== false) { | 526 | if (($pos = array_search($tag, $this->tags ?? [])) !== false) { |
458 | unset($this->tags[$pos]); | 527 | unset($this->tags[$pos]); |
459 | $this->tags = array_values($this->tags); | 528 | $this->tags = array_values($this->tags); |
460 | } | 529 | } |