3 declare(strict_types
=1);
5 namespace Shaarli\Bookmark
;
9 use Shaarli\Bookmark\Exception\InvalidBookmarkException
;
14 * This class represent a single Bookmark with all its attributes.
15 * Every bookmark should manipulated using this, before being formatted.
17 * @package Shaarli\Bookmark
21 /** @var string Date format used in string (former ID format) */
22 const LINK_DATE_FORMAT
= 'Ymd_His';
24 /** @var int Bookmark ID */
27 /** @var string Permalink identifier */
30 /** @var string Bookmark's URL - $shortUrl prefixed with `?` for notes */
33 /** @var string Bookmark's title */
36 /** @var string Raw bookmark's description */
37 protected $description;
39 /** @var array List of bookmark's tags */
42 /** @var string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found */
45 /** @var bool Set to true if the bookmark is set as sticky */
48 /** @var DateTimeInterface Creation datetime */
51 /** @var DateTimeInterface datetime */
54 /** @var bool True if the bookmark can only be seen while logged in */
57 /** @var mixed[] Available to store any additional content for a bookmark. Currently used for search highlight. */
58 protected $additionalContent = [];
61 * Initialize a link from array data. Especially useful to create a Bookmark from former link storage format.
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.
69 public function fromArray(array $data, string $tagsSeparator = ' '): Bookmark
71 $this->id
= $data['id'] ?? null;
72 $this->shortUrl
= $data['shorturl'] ?? null;
73 $this->url
= $data['url'] ?? null;
74 $this->title
= $data['title'] ?? null;
75 $this->description
= $data['description'] ?? null;
76 $this->thumbnail
= $data['thumbnail'] ?? null;
77 $this->sticky
= $data['sticky'] ?? false;
78 $this->created
= $data['created'] ?? null;
79 if (is_array($data['tags'])) {
80 $this->tags
= $data['tags'];
82 $this->tags
= tags_str2array($data['tags'] ?? '', $tagsSeparator);
84 if (! empty($data['updated'])) {
85 $this->updated
= $data['updated'];
87 $this->private = ($data['private'] ?? false) ? true : false;
93 * Make sure that the current instance of Bookmark is valid and can be saved into the data store.
94 * A valid link requires:
96 * - a short URL (for permalinks)
99 * This function also initialize optional empty fields:
100 * - the URL with the permalink
101 * - the title with the URL
103 * Also make sure that we do not save search highlights in the datastore.
105 * @throws InvalidBookmarkException
107 public function validate(): void
109 if ($this->id
=== null
110 || ! is_int($this->id
)
111 || empty($this->shortUrl
)
112 || empty($this->created
)
114 throw new InvalidBookmarkException($this);
116 if (empty($this->url
)) {
117 $this->url
= '/shaare/'. $this->shortUrl
;
119 if (empty($this->title
)) {
120 $this->title
= $this->url
;
122 if (array_key_exists('search_highlight', $this->additionalContent
)) {
123 unset($this->additionalContent
['search_highlight']);
129 * If they're not already initialized, this function also set:
130 * - created: with the current datetime
131 * - shortUrl: with a generated small hash from the date and the given ID
133 * @param int|null $id
137 public function setId(?int $id): Bookmark
140 if (empty($this->created
)) {
141 $this->created
= new DateTime();
143 if (empty($this->shortUrl
)) {
144 $this->shortUrl
= link_small_hash($this->created
, $this->id
);
155 public function getId(): ?int
163 * @return string|null
165 public function getShortUrl(): ?string
167 return $this->shortUrl
;
173 * @return string|null
175 public function getUrl(): ?string
185 public function getTitle(): ?string
191 * Get the Description.
195 public function getDescription(): string
197 return ! empty($this->description
) ? $this->description
: '';
203 * @return DateTimeInterface
205 public function getCreated(): ?DateTimeInterface
207 return $this->created
;
213 * @return DateTimeInterface
215 public function getUpdated(): ?DateTimeInterface
217 return $this->updated
;
223 * @param string|null $shortUrl
227 public function setShortUrl(?string $shortUrl): Bookmark
229 $this->shortUrl
= $shortUrl;
237 * @param string|null $url
238 * @param string[] $allowedProtocols
242 public function setUrl(?string $url, array $allowedProtocols = []): Bookmark
244 $url = $url !== null ? trim($url) : '';
246 $url = whitelist_protocols($url, $allowedProtocols);
256 * @param string|null $title
260 public function setTitle(?string $title): Bookmark
262 $this->title
= $title !== null ? trim($title) : '';
268 * Set the Description.
270 * @param string|null $description
274 public function setDescription(?string $description): Bookmark
276 $this->description
= $description;
283 * Note: you shouldn't set this manually except for special cases (like bookmark import)
285 * @param DateTimeInterface|null $created
289 public function setCreated(?DateTimeInterface
$created): Bookmark
291 $this->created
= $created;
299 * @param DateTimeInterface|null $updated
303 public function setUpdated(?DateTimeInterface
$updated): Bookmark
305 $this->updated
= $updated;
315 public function isPrivate(): bool
317 return $this->private ? true : false;
323 * @param bool|null $private
327 public function setPrivate(?bool $private): Bookmark
329 $this->private = $private ? true : false;
339 public function getTags(): array
341 return is_array($this->tags
) ? $this->tags
: [];
347 * @param string[]|null $tags
351 public function setTags(?array $tags): Bookmark
353 $this->tags
= array_map(
354 function (string $tag): string {
355 return $tag[0] === '-' ? substr($tag, 1) : $tag;
357 tags_filter($tags, ' ')
366 * @return string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found
368 public function getThumbnail()
370 return !$this->isNote() ? $this->thumbnail
: false;
376 * @param string|bool|null $thumbnail Thumbnail's URL - false if no thumbnail could be found
380 public function setThumbnail($thumbnail): Bookmark
382 $this->thumbnail
= $thumbnail;
389 * - the bookmark's thumbnail is not already set to false (= not found)
391 * - it's an HTTP(S) link
392 * - the thumbnail has not yet be retrieved (null) or its associated cache file doesn't exist anymore
394 * @return bool True if the bookmark's thumbnail needs to be retrieved.
396 public function shouldUpdateThumbnail(): bool
398 return $this->thumbnail
!== false
400 && startsWith(strtolower($this->url
), 'http')
401 && (null === $this->thumbnail
|| !is_file($this->thumbnail
))
410 public function isSticky(): bool
412 return $this->sticky
? true : false;
418 * @param bool|null $sticky
422 public function setSticky(?bool $sticky): Bookmark
424 $this->sticky
= $sticky ? true : false;
430 * @param string $separator Tags separator loaded from the config file.
432 * @return string Bookmark's tags as a string, separated by a separator
434 public function getTagsString(string $separator = ' '): string
436 return tags_array2str($this->getTags(), $separator);
442 public function isNote(): bool
444 // We check empty value to get a valid result if the link has not been saved yet
445 return empty($this->url
) || startsWith($this->url
, '/shaare/') || $this->url
[0] === '?';
449 * Set tags from a string.
451 * - tags must be separated whether by a space or a comma
452 * - multiple spaces will be removed
453 * - trailing dash in tags will be removed
455 * @param string|null $tags
456 * @param string $separator Tags separator loaded from the config file.
460 public function setTagsString(?string $tags, string $separator = ' '): Bookmark
462 $this->setTags(tags_str2array($tags, $separator));
468 * Get entire additionalContent array.
472 public function getAdditionalContent(): array
474 return $this->additionalContent
;
478 * Set a single entry in additionalContent, by key.
481 * @param mixed|null $value Any type of value can be set.
485 public function addAdditionalContentEntry(string $key, $value): self
487 $this->additionalContent
[$key] = $value;
493 * Get a single entry in additionalContent, by key.
496 * @param mixed|null $default
498 * @return mixed|null can be any type or even null.
500 public function getAdditionalContentEntry(string $key, $default = null)
502 return array_key_exists($key, $this->additionalContent
) ? $this->additionalContent
[$key] : $default;
506 * Rename a tag in tags list.
508 * @param string $fromTag
509 * @param string $toTag
511 public function renameTag(string $fromTag, string $toTag): void
513 if (($pos = array_search($fromTag, $this->tags
?? [])) !== false) {
514 $this->tags
[$pos] = trim($toTag);
519 * Delete a tag from tags list.
523 public function deleteTag(string $tag): void
525 if (($pos = array_search($tag, $this->tags
?? [])) !== false) {
526 unset($this->tags
[$pos]);
527 $this->tags
= array_values($this->tags
);