aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/bookmark/Bookmark.php
diff options
context:
space:
mode:
Diffstat (limited to 'application/bookmark/Bookmark.php')
-rw-r--r--application/bookmark/Bookmark.php217
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
3declare(strict_types=1);
4
3namespace Shaarli\Bookmark; 5namespace Shaarli\Bookmark;
4 6
5use DateTime; 7use DateTime;
@@ -17,7 +19,7 @@ use Shaarli\Bookmark\Exception\InvalidBookmarkException;
17class Bookmark 19class 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 }