aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/bookmark
diff options
context:
space:
mode:
Diffstat (limited to 'application/bookmark')
-rw-r--r--application/bookmark/Bookmark.php25
-rw-r--r--application/bookmark/BookmarkFileService.php38
-rw-r--r--application/bookmark/BookmarkFilter.php2
-rw-r--r--application/bookmark/BookmarkIO.php10
-rw-r--r--application/bookmark/BookmarkInitializer.php9
-rw-r--r--application/bookmark/BookmarkServiceInterface.php1
-rw-r--r--application/bookmark/LinkUtils.php108
-rw-r--r--application/bookmark/exception/DatastoreNotInitializedException.php10
8 files changed, 62 insertions, 141 deletions
diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php
index f9b21d3d..1beb8be2 100644
--- a/application/bookmark/Bookmark.php
+++ b/application/bookmark/Bookmark.php
@@ -3,6 +3,7 @@
3namespace Shaarli\Bookmark; 3namespace Shaarli\Bookmark;
4 4
5use DateTime; 5use DateTime;
6use DateTimeInterface;
6use Shaarli\Bookmark\Exception\InvalidBookmarkException; 7use Shaarli\Bookmark\Exception\InvalidBookmarkException;
7 8
8/** 9/**
@@ -36,16 +37,16 @@ class Bookmark
36 /** @var array List of bookmark's tags */ 37 /** @var array List of bookmark's tags */
37 protected $tags; 38 protected $tags;
38 39
39 /** @var string Thumbnail's URL - false if no thumbnail could be found */ 40 /** @var string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found */
40 protected $thumbnail; 41 protected $thumbnail;
41 42
42 /** @var bool Set to true if the bookmark is set as sticky */ 43 /** @var bool Set to true if the bookmark is set as sticky */
43 protected $sticky; 44 protected $sticky;
44 45
45 /** @var DateTime Creation datetime */ 46 /** @var DateTimeInterface Creation datetime */
46 protected $created; 47 protected $created;
47 48
48 /** @var DateTime Update datetime */ 49 /** @var DateTimeInterface datetime */
49 protected $updated; 50 protected $updated;
50 51
51 /** @var bool True if the bookmark can only be seen while logged in */ 52 /** @var bool True if the bookmark can only be seen while logged in */
@@ -100,12 +101,12 @@ class Bookmark
100 || ! is_int($this->id) 101 || ! is_int($this->id)
101 || empty($this->shortUrl) 102 || empty($this->shortUrl)
102 || empty($this->created) 103 || empty($this->created)
103 || ! $this->created instanceof DateTime 104 || ! $this->created instanceof DateTimeInterface
104 ) { 105 ) {
105 throw new InvalidBookmarkException($this); 106 throw new InvalidBookmarkException($this);
106 } 107 }
107 if (empty($this->url)) { 108 if (empty($this->url)) {
108 $this->url = '?'. $this->shortUrl; 109 $this->url = '/shaare/'. $this->shortUrl;
109 } 110 }
110 if (empty($this->title)) { 111 if (empty($this->title)) {
111 $this->title = $this->url; 112 $this->title = $this->url;
@@ -188,7 +189,7 @@ class Bookmark
188 /** 189 /**
189 * Get the Created. 190 * Get the Created.
190 * 191 *
191 * @return DateTime 192 * @return DateTimeInterface
192 */ 193 */
193 public function getCreated() 194 public function getCreated()
194 { 195 {
@@ -198,7 +199,7 @@ class Bookmark
198 /** 199 /**
199 * Get the Updated. 200 * Get the Updated.
200 * 201 *
201 * @return DateTime 202 * @return DateTimeInterface
202 */ 203 */
203 public function getUpdated() 204 public function getUpdated()
204 { 205 {
@@ -270,7 +271,7 @@ class Bookmark
270 * Set the Created. 271 * Set the Created.
271 * Note: you shouldn't set this manually except for special cases (like bookmark import) 272 * Note: you shouldn't set this manually except for special cases (like bookmark import)
272 * 273 *
273 * @param DateTime $created 274 * @param DateTimeInterface $created
274 * 275 *
275 * @return Bookmark 276 * @return Bookmark
276 */ 277 */
@@ -284,7 +285,7 @@ class Bookmark
284 /** 285 /**
285 * Set the Updated. 286 * Set the Updated.
286 * 287 *
287 * @param DateTime $updated 288 * @param DateTimeInterface $updated
288 * 289 *
289 * @return Bookmark 290 * @return Bookmark
290 */ 291 */
@@ -346,7 +347,7 @@ class Bookmark
346 /** 347 /**
347 * Get the Thumbnail. 348 * Get the Thumbnail.
348 * 349 *
349 * @return string|bool 350 * @return string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found
350 */ 351 */
351 public function getThumbnail() 352 public function getThumbnail()
352 { 353 {
@@ -356,7 +357,7 @@ class Bookmark
356 /** 357 /**
357 * Set the Thumbnail. 358 * Set the Thumbnail.
358 * 359 *
359 * @param string|bool $thumbnail 360 * @param string|bool $thumbnail Thumbnail's URL - false if no thumbnail could be found
360 * 361 *
361 * @return Bookmark 362 * @return Bookmark
362 */ 363 */
@@ -405,7 +406,7 @@ class Bookmark
405 public function isNote() 406 public function isNote()
406 { 407 {
407 // We check empty value to get a valid result if the link has not been saved yet 408 // We check empty value to get a valid result if the link has not been saved yet
408 return empty($this->url) || $this->url[0] === '?'; 409 return empty($this->url) || startsWith($this->url, '/shaare/') || $this->url[0] === '?';
409 } 410 }
410 411
411 /** 412 /**
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php
index 9c59e139..b3a90ed4 100644
--- a/application/bookmark/BookmarkFileService.php
+++ b/application/bookmark/BookmarkFileService.php
@@ -6,12 +6,14 @@ namespace Shaarli\Bookmark;
6 6
7use Exception; 7use Exception;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException; 8use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
9use Shaarli\Bookmark\Exception\DatastoreNotInitializedException;
9use Shaarli\Bookmark\Exception\EmptyDataStoreException; 10use Shaarli\Bookmark\Exception\EmptyDataStoreException;
10use Shaarli\Config\ConfigManager; 11use Shaarli\Config\ConfigManager;
11use Shaarli\Formatter\BookmarkMarkdownFormatter; 12use Shaarli\Formatter\BookmarkMarkdownFormatter;
12use Shaarli\History; 13use Shaarli\History;
13use Shaarli\Legacy\LegacyLinkDB; 14use Shaarli\Legacy\LegacyLinkDB;
14use Shaarli\Legacy\LegacyUpdater; 15use Shaarli\Legacy\LegacyUpdater;
16use Shaarli\Render\PageCacheManager;
15use Shaarli\Updater\UpdaterUtils; 17use Shaarli\Updater\UpdaterUtils;
16 18
17/** 19/**
@@ -39,6 +41,9 @@ class BookmarkFileService implements BookmarkServiceInterface
39 /** @var History instance */ 41 /** @var History instance */
40 protected $history; 42 protected $history;
41 43
44 /** @var PageCacheManager instance */
45 protected $pageCacheManager;
46
42 /** @var bool true for logged in users. Default value to retrieve private bookmarks. */ 47 /** @var bool true for logged in users. Default value to retrieve private bookmarks. */
43 protected $isLoggedIn; 48 protected $isLoggedIn;
44 49
@@ -49,6 +54,7 @@ class BookmarkFileService implements BookmarkServiceInterface
49 { 54 {
50 $this->conf = $conf; 55 $this->conf = $conf;
51 $this->history = $history; 56 $this->history = $history;
57 $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn);
52 $this->bookmarksIO = new BookmarkIO($this->conf); 58 $this->bookmarksIO = new BookmarkIO($this->conf);
53 $this->isLoggedIn = $isLoggedIn; 59 $this->isLoggedIn = $isLoggedIn;
54 60
@@ -57,10 +63,16 @@ class BookmarkFileService implements BookmarkServiceInterface
57 } else { 63 } else {
58 try { 64 try {
59 $this->bookmarks = $this->bookmarksIO->read(); 65 $this->bookmarks = $this->bookmarksIO->read();
60 } catch (EmptyDataStoreException $e) { 66 } catch (EmptyDataStoreException|DatastoreNotInitializedException $e) {
61 $this->bookmarks = new BookmarkArray(); 67 $this->bookmarks = new BookmarkArray();
62 if ($isLoggedIn) { 68
63 $this->save(); 69 if ($this->isLoggedIn) {
70 // Datastore file does not exists, we initialize it with default bookmarks.
71 if ($e instanceof DatastoreNotInitializedException) {
72 $this->initialize();
73 } else {
74 $this->save();
75 }
64 } 76 }
65 } 77 }
66 78
@@ -88,7 +100,7 @@ class BookmarkFileService implements BookmarkServiceInterface
88 throw new Exception('Not authorized'); 100 throw new Exception('Not authorized');
89 } 101 }
90 102
91 return $bookmark; 103 return $first;
92 } 104 }
93 105
94 /** 106 /**
@@ -149,7 +161,7 @@ class BookmarkFileService implements BookmarkServiceInterface
149 */ 161 */
150 public function set($bookmark, $save = true) 162 public function set($bookmark, $save = true)
151 { 163 {
152 if ($this->isLoggedIn !== true) { 164 if (true !== $this->isLoggedIn) {
153 throw new Exception(t('You\'re not authorized to alter the datastore')); 165 throw new Exception(t('You\'re not authorized to alter the datastore'));
154 } 166 }
155 if (! $bookmark instanceof Bookmark) { 167 if (! $bookmark instanceof Bookmark) {
@@ -174,7 +186,7 @@ class BookmarkFileService implements BookmarkServiceInterface
174 */ 186 */
175 public function add($bookmark, $save = true) 187 public function add($bookmark, $save = true)
176 { 188 {
177 if ($this->isLoggedIn !== true) { 189 if (true !== $this->isLoggedIn) {
178 throw new Exception(t('You\'re not authorized to alter the datastore')); 190 throw new Exception(t('You\'re not authorized to alter the datastore'));
179 } 191 }
180 if (! $bookmark instanceof Bookmark) { 192 if (! $bookmark instanceof Bookmark) {
@@ -199,7 +211,7 @@ class BookmarkFileService implements BookmarkServiceInterface
199 */ 211 */
200 public function addOrSet($bookmark, $save = true) 212 public function addOrSet($bookmark, $save = true)
201 { 213 {
202 if ($this->isLoggedIn !== true) { 214 if (true !== $this->isLoggedIn) {
203 throw new Exception(t('You\'re not authorized to alter the datastore')); 215 throw new Exception(t('You\'re not authorized to alter the datastore'));
204 } 216 }
205 if (! $bookmark instanceof Bookmark) { 217 if (! $bookmark instanceof Bookmark) {
@@ -216,7 +228,7 @@ class BookmarkFileService implements BookmarkServiceInterface
216 */ 228 */
217 public function remove($bookmark, $save = true) 229 public function remove($bookmark, $save = true)
218 { 230 {
219 if ($this->isLoggedIn !== true) { 231 if (true !== $this->isLoggedIn) {
220 throw new Exception(t('You\'re not authorized to alter the datastore')); 232 throw new Exception(t('You\'re not authorized to alter the datastore'));
221 } 233 }
222 if (! $bookmark instanceof Bookmark) { 234 if (! $bookmark instanceof Bookmark) {
@@ -269,13 +281,14 @@ class BookmarkFileService implements BookmarkServiceInterface
269 */ 281 */
270 public function save() 282 public function save()
271 { 283 {
272 if (!$this->isLoggedIn) { 284 if (true !== $this->isLoggedIn) {
273 // TODO: raise an Exception instead 285 // TODO: raise an Exception instead
274 die('You are not authorized to change the database.'); 286 die('You are not authorized to change the database.');
275 } 287 }
288
276 $this->bookmarks->reorder(); 289 $this->bookmarks->reorder();
277 $this->bookmarksIO->write($this->bookmarks); 290 $this->bookmarksIO->write($this->bookmarks);
278 invalidateCaches($this->conf->get('resource.page_cache')); 291 $this->pageCacheManager->invalidateCaches();
279 } 292 }
280 293
281 /** 294 /**
@@ -291,6 +304,7 @@ class BookmarkFileService implements BookmarkServiceInterface
291 if (empty($tag) 304 if (empty($tag)
292 || (! $this->isLoggedIn && startsWith($tag, '.')) 305 || (! $this->isLoggedIn && startsWith($tag, '.'))
293 || $tag === BookmarkMarkdownFormatter::NO_MD_TAG 306 || $tag === BookmarkMarkdownFormatter::NO_MD_TAG
307 || in_array($tag, $filteringTags, true)
294 ) { 308 ) {
295 continue; 309 continue;
296 } 310 }
@@ -349,6 +363,10 @@ class BookmarkFileService implements BookmarkServiceInterface
349 { 363 {
350 $initializer = new BookmarkInitializer($this); 364 $initializer = new BookmarkInitializer($this);
351 $initializer->initialize(); 365 $initializer->initialize();
366
367 if (true === $this->isLoggedIn) {
368 $this->save();
369 }
352 } 370 }
353 371
354 /** 372 /**
diff --git a/application/bookmark/BookmarkFilter.php b/application/bookmark/BookmarkFilter.php
index fd556679..797a36b8 100644
--- a/application/bookmark/BookmarkFilter.php
+++ b/application/bookmark/BookmarkFilter.php
@@ -436,7 +436,7 @@ class BookmarkFilter
436 throw new Exception('Invalid date format'); 436 throw new Exception('Invalid date format');
437 } 437 }
438 438
439 $filtered = array(); 439 $filtered = [];
440 foreach ($this->bookmarks as $key => $l) { 440 foreach ($this->bookmarks as $key => $l) {
441 if ($l->getCreated()->format('Ymd') == $day) { 441 if ($l->getCreated()->format('Ymd') == $day) {
442 $filtered[$key] = $l; 442 $filtered[$key] = $l;
diff --git a/application/bookmark/BookmarkIO.php b/application/bookmark/BookmarkIO.php
index ae9ffcb4..6bf7f365 100644
--- a/application/bookmark/BookmarkIO.php
+++ b/application/bookmark/BookmarkIO.php
@@ -2,6 +2,7 @@
2 2
3namespace Shaarli\Bookmark; 3namespace Shaarli\Bookmark;
4 4
5use Shaarli\Bookmark\Exception\DatastoreNotInitializedException;
5use Shaarli\Bookmark\Exception\EmptyDataStoreException; 6use Shaarli\Bookmark\Exception\EmptyDataStoreException;
6use Shaarli\Bookmark\Exception\NotWritableDataStoreException; 7use Shaarli\Bookmark\Exception\NotWritableDataStoreException;
7use Shaarli\Config\ConfigManager; 8use Shaarli\Config\ConfigManager;
@@ -52,13 +53,14 @@ class BookmarkIO
52 * 53 *
53 * @return BookmarkArray instance 54 * @return BookmarkArray instance
54 * 55 *
55 * @throws NotWritableDataStoreException Data couldn't be loaded 56 * @throws NotWritableDataStoreException Data couldn't be loaded
56 * @throws EmptyDataStoreException Datastore doesn't exist 57 * @throws EmptyDataStoreException Datastore file exists but does not contain any bookmark
58 * @throws DatastoreNotInitializedException File does not exists
57 */ 59 */
58 public function read() 60 public function read()
59 { 61 {
60 if (! file_exists($this->datastore)) { 62 if (! file_exists($this->datastore)) {
61 throw new EmptyDataStoreException(); 63 throw new DatastoreNotInitializedException();
62 } 64 }
63 65
64 if (!is_writable($this->datastore)) { 66 if (!is_writable($this->datastore)) {
@@ -102,7 +104,5 @@ class BookmarkIO
102 $this->datastore, 104 $this->datastore,
103 self::$phpPrefix.base64_encode(gzdeflate(serialize($links))).self::$phpSuffix 105 self::$phpPrefix.base64_encode(gzdeflate(serialize($links))).self::$phpSuffix
104 ); 106 );
105
106 invalidateCaches($this->conf->get('resource.page_cache'));
107 } 107 }
108} 108}
diff --git a/application/bookmark/BookmarkInitializer.php b/application/bookmark/BookmarkInitializer.php
index 9eee9a35..cd2d1724 100644
--- a/application/bookmark/BookmarkInitializer.php
+++ b/application/bookmark/BookmarkInitializer.php
@@ -6,8 +6,7 @@ namespace Shaarli\Bookmark;
6 * Class BookmarkInitializer 6 * Class BookmarkInitializer
7 * 7 *
8 * This class is used to initialized default bookmarks after a fresh install of Shaarli. 8 * This class is used to initialized default bookmarks after a fresh install of Shaarli.
9 * It is no longer call when the data store is empty, 9 * It should be only called if the datastore file does not exist(users might want to delete the default bookmarks).
10 * because user might want to delete default bookmarks after the install.
11 * 10 *
12 * To prevent data corruption, it does not overwrite existing bookmarks, 11 * To prevent data corruption, it does not overwrite existing bookmarks,
13 * even though there should not be any. 12 * even though there should not be any.
@@ -36,11 +35,11 @@ class BookmarkInitializer
36 { 35 {
37 $bookmark = new Bookmark(); 36 $bookmark = new Bookmark();
38 $bookmark->setTitle(t('My secret stuff... - Pastebin.com')); 37 $bookmark->setTitle(t('My secret stuff... - Pastebin.com'));
39 $bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', []); 38 $bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=');
40 $bookmark->setDescription(t('Shhhh! I\'m a private link only YOU can see. You can delete me too.')); 39 $bookmark->setDescription(t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'));
41 $bookmark->setTagsString('secretstuff'); 40 $bookmark->setTagsString('secretstuff');
42 $bookmark->setPrivate(true); 41 $bookmark->setPrivate(true);
43 $this->bookmarkService->add($bookmark); 42 $this->bookmarkService->add($bookmark, false);
44 43
45 $bookmark = new Bookmark(); 44 $bookmark = new Bookmark();
46 $bookmark->setTitle(t('The personal, minimalist, super-fast, database free, bookmarking service')); 45 $bookmark->setTitle(t('The personal, minimalist, super-fast, database free, bookmarking service'));
@@ -54,6 +53,6 @@ To learn how to use Shaarli, consult the link "Documentation" at the bottom of t
54You use the community supported version of the original Shaarli project, by Sebastien Sauvage.' 53You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
55 )); 54 ));
56 $bookmark->setTagsString('opensource software'); 55 $bookmark->setTagsString('opensource software');
57 $this->bookmarkService->add($bookmark); 56 $this->bookmarkService->add($bookmark, false);
58 } 57 }
59} 58}
diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php
index 7b7a4f09..ce8bd912 100644
--- a/application/bookmark/BookmarkServiceInterface.php
+++ b/application/bookmark/BookmarkServiceInterface.php
@@ -6,7 +6,6 @@ namespace Shaarli\Bookmark;
6use Shaarli\Bookmark\Exception\BookmarkNotFoundException; 6use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
7use Shaarli\Bookmark\Exception\NotWritableDataStoreException; 7use Shaarli\Bookmark\Exception\NotWritableDataStoreException;
8use Shaarli\Config\ConfigManager; 8use Shaarli\Config\ConfigManager;
9use Shaarli\Exceptions\IOException;
10use Shaarli\History; 9use Shaarli\History;
11 10
12/** 11/**
diff --git a/application/bookmark/LinkUtils.php b/application/bookmark/LinkUtils.php
index 88379430..68914fca 100644
--- a/application/bookmark/LinkUtils.php
+++ b/application/bookmark/LinkUtils.php
@@ -3,112 +3,6 @@
3use Shaarli\Bookmark\Bookmark; 3use Shaarli\Bookmark\Bookmark;
4 4
5/** 5/**
6 * Get cURL callback function for CURLOPT_WRITEFUNCTION
7 *
8 * @param string $charset to extract from the downloaded page (reference)
9 * @param string $title to extract from the downloaded page (reference)
10 * @param string $description to extract from the downloaded page (reference)
11 * @param string $keywords to extract from the downloaded page (reference)
12 * @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content
13 * @param string $curlGetInfo Optionally overrides curl_getinfo function
14 *
15 * @return Closure
16 */
17function get_curl_download_callback(
18 &$charset,
19 &$title,
20 &$description,
21 &$keywords,
22 $retrieveDescription,
23 $curlGetInfo = 'curl_getinfo'
24) {
25 $isRedirected = false;
26 $currentChunk = 0;
27 $foundChunk = null;
28
29 /**
30 * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
31 *
32 * While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text'
33 * Then we extract the title and the charset and stop the download when it's done.
34 *
35 * @param resource $ch cURL resource
36 * @param string $data chunk of data being downloaded
37 *
38 * @return int|bool length of $data or false if we need to stop the download
39 */
40 return function (&$ch, $data) use (
41 $retrieveDescription,
42 $curlGetInfo,
43 &$charset,
44 &$title,
45 &$description,
46 &$keywords,
47 &$isRedirected,
48 &$currentChunk,
49 &$foundChunk
50 ) {
51 $currentChunk++;
52 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
53 if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
54 $isRedirected = true;
55 return strlen($data);
56 }
57 if (!empty($responseCode) && $responseCode !== 200) {
58 return false;
59 }
60 // After a redirection, the content type will keep the previous request value
61 // until it finds the next content-type header.
62 if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) {
63 $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
64 }
65 if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
66 return false;
67 }
68 if (!empty($contentType) && empty($charset)) {
69 $charset = header_extract_charset($contentType);
70 }
71 if (empty($charset)) {
72 $charset = html_extract_charset($data);
73 }
74 if (empty($title)) {
75 $title = html_extract_title($data);
76 $foundChunk = ! empty($title) ? $currentChunk : $foundChunk;
77 }
78 if ($retrieveDescription && empty($description)) {
79 $description = html_extract_tag('description', $data);
80 $foundChunk = ! empty($description) ? $currentChunk : $foundChunk;
81 }
82 if ($retrieveDescription && empty($keywords)) {
83 $keywords = html_extract_tag('keywords', $data);
84 if (! empty($keywords)) {
85 $foundChunk = $currentChunk;
86 // Keywords use the format tag1, tag2 multiple words, tag
87 // So we format them to match Shaarli's separator and glue multiple words with '-'
88 $keywords = implode(' ', array_map(function($keyword) {
89 return implode('-', preg_split('/\s+/', trim($keyword)));
90 }, explode(',', $keywords)));
91 }
92 }
93
94 // We got everything we want, stop the download.
95 // If we already found either the title, description or keywords,
96 // it's highly unlikely that we'll found the other metas further than
97 // in the same chunk of data or the next one. So we also stop the download after that.
98 if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null
99 && (! $retrieveDescription
100 || $foundChunk < $currentChunk
101 || (!empty($title) && !empty($description) && !empty($keywords))
102 )
103 ) {
104 return false;
105 }
106
107 return strlen($data);
108 };
109}
110
111/**
112 * Extract title from an HTML document. 6 * Extract title from an HTML document.
113 * 7 *
114 * @param string $html HTML content where to look for a title. 8 * @param string $html HTML content where to look for a title.
@@ -220,7 +114,7 @@ function hashtag_autolink($description, $indexUrl = '')
220 * \p{Mn} - any non marking space (accents, umlauts, etc) 114 * \p{Mn} - any non marking space (accents, umlauts, etc)
221 */ 115 */
222 $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; 116 $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui';
223 $replacement = '$1<a href="'. $indexUrl .'?addtag=$2" title="Hashtag $2">#$2</a>'; 117 $replacement = '$1<a href="'. $indexUrl .'./add-tag/$2" title="Hashtag $2">#$2</a>';
224 return preg_replace($regex, $replacement, $description); 118 return preg_replace($regex, $replacement, $description);
225} 119}
226 120
diff --git a/application/bookmark/exception/DatastoreNotInitializedException.php b/application/bookmark/exception/DatastoreNotInitializedException.php
new file mode 100644
index 00000000..f495049d
--- /dev/null
+++ b/application/bookmark/exception/DatastoreNotInitializedException.php
@@ -0,0 +1,10 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Bookmark\Exception;
6
7class DatastoreNotInitializedException extends \Exception
8{
9
10}