aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/bookmark/BookmarkFileService.php
diff options
context:
space:
mode:
Diffstat (limited to 'application/bookmark/BookmarkFileService.php')
-rw-r--r--application/bookmark/BookmarkFileService.php157
1 files changed, 100 insertions, 57 deletions
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php
index 9c59e139..3ea98a45 100644
--- a/application/bookmark/BookmarkFileService.php
+++ b/application/bookmark/BookmarkFileService.php
@@ -1,17 +1,21 @@
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;
9use malkusch\lock\mutex\Mutex;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException; 10use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
11use Shaarli\Bookmark\Exception\DatastoreNotInitializedException;
9use Shaarli\Bookmark\Exception\EmptyDataStoreException; 12use Shaarli\Bookmark\Exception\EmptyDataStoreException;
10use Shaarli\Config\ConfigManager; 13use Shaarli\Config\ConfigManager;
11use Shaarli\Formatter\BookmarkMarkdownFormatter; 14use Shaarli\Formatter\BookmarkMarkdownFormatter;
12use Shaarli\History; 15use Shaarli\History;
13use Shaarli\Legacy\LegacyLinkDB; 16use Shaarli\Legacy\LegacyLinkDB;
14use Shaarli\Legacy\LegacyUpdater; 17use Shaarli\Legacy\LegacyUpdater;
18use Shaarli\Render\PageCacheManager;
15use Shaarli\Updater\UpdaterUtils; 19use Shaarli\Updater\UpdaterUtils;
16 20
17/** 21/**
@@ -39,17 +43,25 @@ class BookmarkFileService implements BookmarkServiceInterface
39 /** @var History instance */ 43 /** @var History instance */
40 protected $history; 44 protected $history;
41 45
46 /** @var PageCacheManager instance */
47 protected $pageCacheManager;
48
42 /** @var bool true for logged in users. Default value to retrieve private bookmarks. */ 49 /** @var bool true for logged in users. Default value to retrieve private bookmarks. */
43 protected $isLoggedIn; 50 protected $isLoggedIn;
44 51
52 /** @var Mutex */
53 protected $mutex;
54
45 /** 55 /**
46 * @inheritDoc 56 * @inheritDoc
47 */ 57 */
48 public function __construct(ConfigManager $conf, History $history, $isLoggedIn) 58 public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
49 { 59 {
50 $this->conf = $conf; 60 $this->conf = $conf;
51 $this->history = $history; 61 $this->history = $history;
52 $this->bookmarksIO = new BookmarkIO($this->conf); 62 $this->mutex = $mutex;
63 $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn);
64 $this->bookmarksIO = new BookmarkIO($this->conf, $this->mutex);
53 $this->isLoggedIn = $isLoggedIn; 65 $this->isLoggedIn = $isLoggedIn;
54 66
55 if (!$this->isLoggedIn && $this->conf->get('privacy.hide_public_links', false)) { 67 if (!$this->isLoggedIn && $this->conf->get('privacy.hide_public_links', false)) {
@@ -57,10 +69,16 @@ class BookmarkFileService implements BookmarkServiceInterface
57 } else { 69 } else {
58 try { 70 try {
59 $this->bookmarks = $this->bookmarksIO->read(); 71 $this->bookmarks = $this->bookmarksIO->read();
60 } catch (EmptyDataStoreException $e) { 72 } catch (EmptyDataStoreException|DatastoreNotInitializedException $e) {
61 $this->bookmarks = new BookmarkArray(); 73 $this->bookmarks = new BookmarkArray();
62 if ($isLoggedIn) { 74
63 $this->save(); 75 if ($this->isLoggedIn) {
76 // Datastore file does not exists, we initialize it with default bookmarks.
77 if ($e instanceof DatastoreNotInitializedException) {
78 $this->initialize();
79 } else {
80 $this->save();
81 }
64 } 82 }
65 } 83 }
66 84
@@ -79,22 +97,25 @@ class BookmarkFileService implements BookmarkServiceInterface
79 /** 97 /**
80 * @inheritDoc 98 * @inheritDoc
81 */ 99 */
82 public function findByHash($hash) 100 public function findByHash(string $hash, string $privateKey = null): Bookmark
83 { 101 {
84 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash); 102 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash);
85 // PHP 7.3 introduced array_key_first() to avoid this hack 103 // PHP 7.3 introduced array_key_first() to avoid this hack
86 $first = reset($bookmark); 104 $first = reset($bookmark);
87 if (! $this->isLoggedIn && $first->isPrivate()) { 105 if (!$this->isLoggedIn
88 throw new Exception('Not authorized'); 106 && $first->isPrivate()
107 && (empty($privateKey) || $privateKey !== $first->getAdditionalContentEntry('private_key'))
108 ) {
109 throw new BookmarkNotFoundException();
89 } 110 }
90 111
91 return $bookmark; 112 return $first;
92 } 113 }
93 114
94 /** 115 /**
95 * @inheritDoc 116 * @inheritDoc
96 */ 117 */
97 public function findByUrl($url) 118 public function findByUrl(string $url): ?Bookmark
98 { 119 {
99 return $this->bookmarks->getByUrl($url); 120 return $this->bookmarks->getByUrl($url);
100 } 121 }
@@ -102,19 +123,28 @@ class BookmarkFileService implements BookmarkServiceInterface
102 /** 123 /**
103 * @inheritDoc 124 * @inheritDoc
104 */ 125 */
105 public function search($request = [], $visibility = null, $caseSensitive = false, $untaggedOnly = false) 126 public function search(
106 { 127 array $request = [],
128 string $visibility = null,
129 bool $caseSensitive = false,
130 bool $untaggedOnly = false,
131 bool $ignoreSticky = false
132 ) {
107 if ($visibility === null) { 133 if ($visibility === null) {
108 $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC; 134 $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
109 } 135 }
110 136
111 // Filter bookmark database according to parameters. 137 // Filter bookmark database according to parameters.
112 $searchtags = isset($request['searchtags']) ? $request['searchtags'] : ''; 138 $searchTags = isset($request['searchtags']) ? $request['searchtags'] : '';
113 $searchterm = isset($request['searchterm']) ? $request['searchterm'] : ''; 139 $searchTerm = isset($request['searchterm']) ? $request['searchterm'] : '';
140
141 if ($ignoreSticky) {
142 $this->bookmarks->reorder('DESC', true);
143 }
114 144
115 return $this->bookmarkFilter->filter( 145 return $this->bookmarkFilter->filter(
116 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, 146 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
117 [$searchtags, $searchterm], 147 [$searchTags, $searchTerm],
118 $caseSensitive, 148 $caseSensitive,
119 $visibility, 149 $visibility,
120 $untaggedOnly 150 $untaggedOnly
@@ -124,7 +154,7 @@ class BookmarkFileService implements BookmarkServiceInterface
124 /** 154 /**
125 * @inheritDoc 155 * @inheritDoc
126 */ 156 */
127 public function get($id, $visibility = null) 157 public function get(int $id, string $visibility = null): Bookmark
128 { 158 {
129 if (! isset($this->bookmarks[$id])) { 159 if (! isset($this->bookmarks[$id])) {
130 throw new BookmarkNotFoundException(); 160 throw new BookmarkNotFoundException();
@@ -147,20 +177,17 @@ class BookmarkFileService implements BookmarkServiceInterface
147 /** 177 /**
148 * @inheritDoc 178 * @inheritDoc
149 */ 179 */
150 public function set($bookmark, $save = true) 180 public function set(Bookmark $bookmark, bool $save = true): Bookmark
151 { 181 {
152 if ($this->isLoggedIn !== true) { 182 if (true !== $this->isLoggedIn) {
153 throw new Exception(t('You\'re not authorized to alter the datastore')); 183 throw new Exception(t('You\'re not authorized to alter the datastore'));
154 } 184 }
155 if (! $bookmark instanceof Bookmark) {
156 throw new Exception(t('Provided data is invalid'));
157 }
158 if (! isset($this->bookmarks[$bookmark->getId()])) { 185 if (! isset($this->bookmarks[$bookmark->getId()])) {
159 throw new BookmarkNotFoundException(); 186 throw new BookmarkNotFoundException();
160 } 187 }
161 $bookmark->validate(); 188 $bookmark->validate();
162 189
163 $bookmark->setUpdated(new \DateTime()); 190 $bookmark->setUpdated(new DateTime());
164 $this->bookmarks[$bookmark->getId()] = $bookmark; 191 $this->bookmarks[$bookmark->getId()] = $bookmark;
165 if ($save === true) { 192 if ($save === true) {
166 $this->save(); 193 $this->save();
@@ -172,15 +199,12 @@ class BookmarkFileService implements BookmarkServiceInterface
172 /** 199 /**
173 * @inheritDoc 200 * @inheritDoc
174 */ 201 */
175 public function add($bookmark, $save = true) 202 public function add(Bookmark $bookmark, bool $save = true): Bookmark
176 { 203 {
177 if ($this->isLoggedIn !== true) { 204 if (true !== $this->isLoggedIn) {
178 throw new Exception(t('You\'re not authorized to alter the datastore')); 205 throw new Exception(t('You\'re not authorized to alter the datastore'));
179 } 206 }
180 if (! $bookmark instanceof Bookmark) { 207 if (!empty($bookmark->getId())) {
181 throw new Exception(t('Provided data is invalid'));
182 }
183 if (! empty($bookmark->getId())) {
184 throw new Exception(t('This bookmarks already exists')); 208 throw new Exception(t('This bookmarks already exists'));
185 } 209 }
186 $bookmark->setId($this->bookmarks->getNextId()); 210 $bookmark->setId($this->bookmarks->getNextId());
@@ -197,14 +221,11 @@ class BookmarkFileService implements BookmarkServiceInterface
197 /** 221 /**
198 * @inheritDoc 222 * @inheritDoc
199 */ 223 */
200 public function addOrSet($bookmark, $save = true) 224 public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark
201 { 225 {
202 if ($this->isLoggedIn !== true) { 226 if (true !== $this->isLoggedIn) {
203 throw new Exception(t('You\'re not authorized to alter the datastore')); 227 throw new Exception(t('You\'re not authorized to alter the datastore'));
204 } 228 }
205 if (! $bookmark instanceof Bookmark) {
206 throw new Exception('Provided data is invalid');
207 }
208 if ($bookmark->getId() === null) { 229 if ($bookmark->getId() === null) {
209 return $this->add($bookmark, $save); 230 return $this->add($bookmark, $save);
210 } 231 }
@@ -214,14 +235,11 @@ class BookmarkFileService implements BookmarkServiceInterface
214 /** 235 /**
215 * @inheritDoc 236 * @inheritDoc
216 */ 237 */
217 public function remove($bookmark, $save = true) 238 public function remove(Bookmark $bookmark, bool $save = true): void
218 { 239 {
219 if ($this->isLoggedIn !== true) { 240 if (true !== $this->isLoggedIn) {
220 throw new Exception(t('You\'re not authorized to alter the datastore')); 241 throw new Exception(t('You\'re not authorized to alter the datastore'));
221 } 242 }
222 if (! $bookmark instanceof Bookmark) {
223 throw new Exception(t('Provided data is invalid'));
224 }
225 if (! isset($this->bookmarks[$bookmark->getId()])) { 243 if (! isset($this->bookmarks[$bookmark->getId()])) {
226 throw new BookmarkNotFoundException(); 244 throw new BookmarkNotFoundException();
227 } 245 }
@@ -236,7 +254,7 @@ class BookmarkFileService implements BookmarkServiceInterface
236 /** 254 /**
237 * @inheritDoc 255 * @inheritDoc
238 */ 256 */
239 public function exists($id, $visibility = null) 257 public function exists(int $id, string $visibility = null): bool
240 { 258 {
241 if (! isset($this->bookmarks[$id])) { 259 if (! isset($this->bookmarks[$id])) {
242 return false; 260 return false;
@@ -259,7 +277,7 @@ class BookmarkFileService implements BookmarkServiceInterface
259 /** 277 /**
260 * @inheritDoc 278 * @inheritDoc
261 */ 279 */
262 public function count($visibility = null) 280 public function count(string $visibility = null): int
263 { 281 {
264 return count($this->search([], $visibility)); 282 return count($this->search([], $visibility));
265 } 283 }
@@ -267,21 +285,22 @@ class BookmarkFileService implements BookmarkServiceInterface
267 /** 285 /**
268 * @inheritDoc 286 * @inheritDoc
269 */ 287 */
270 public function save() 288 public function save(): void
271 { 289 {
272 if (!$this->isLoggedIn) { 290 if (true !== $this->isLoggedIn) {
273 // TODO: raise an Exception instead 291 // TODO: raise an Exception instead
274 die('You are not authorized to change the database.'); 292 die('You are not authorized to change the database.');
275 } 293 }
294
276 $this->bookmarks->reorder(); 295 $this->bookmarks->reorder();
277 $this->bookmarksIO->write($this->bookmarks); 296 $this->bookmarksIO->write($this->bookmarks);
278 invalidateCaches($this->conf->get('resource.page_cache')); 297 $this->pageCacheManager->invalidateCaches();
279 } 298 }
280 299
281 /** 300 /**
282 * @inheritDoc 301 * @inheritDoc
283 */ 302 */
284 public function bookmarksCountPerTag($filteringTags = [], $visibility = null) 303 public function bookmarksCountPerTag(array $filteringTags = [], string $visibility = null): array
285 { 304 {
286 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility); 305 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
287 $tags = []; 306 $tags = [];
@@ -291,6 +310,7 @@ class BookmarkFileService implements BookmarkServiceInterface
291 if (empty($tag) 310 if (empty($tag)
292 || (! $this->isLoggedIn && startsWith($tag, '.')) 311 || (! $this->isLoggedIn && startsWith($tag, '.'))
293 || $tag === BookmarkMarkdownFormatter::NO_MD_TAG 312 || $tag === BookmarkMarkdownFormatter::NO_MD_TAG
313 || in_array($tag, $filteringTags, true)
294 ) { 314 ) {
295 continue; 315 continue;
296 } 316 }
@@ -316,45 +336,68 @@ class BookmarkFileService implements BookmarkServiceInterface
316 $keys = array_keys($tags); 336 $keys = array_keys($tags);
317 $tmpTags = array_combine($keys, $keys); 337 $tmpTags = array_combine($keys, $keys);
318 array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags); 338 array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
339
319 return $tags; 340 return $tags;
320 } 341 }
321 342
322 /** 343 /**
323 * @inheritDoc 344 * @inheritDoc
324 */ 345 */
325 public function days() 346 public function findByDate(
326 { 347 \DateTimeInterface $from,
327 $bookmarkDays = []; 348 \DateTimeInterface $to,
328 foreach ($this->search() as $bookmark) { 349 ?\DateTimeInterface &$previous,
329 $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0; 350 ?\DateTimeInterface &$next
351 ): array {
352 $out = [];
353 $previous = null;
354 $next = null;
355
356 foreach ($this->search([], null, false, false, true) as $bookmark) {
357 if ($to < $bookmark->getCreated()) {
358 $next = $bookmark->getCreated();
359 } else if ($from < $bookmark->getCreated() && $to > $bookmark->getCreated()) {
360 $out[] = $bookmark;
361 } else {
362 if ($previous !== null) {
363 break;
364 }
365 $previous = $bookmark->getCreated();
366 }
330 } 367 }
331 $bookmarkDays = array_keys($bookmarkDays);
332 sort($bookmarkDays);
333 368
334 return $bookmarkDays; 369 return $out;
335 } 370 }
336 371
337 /** 372 /**
338 * @inheritDoc 373 * @inheritDoc
339 */ 374 */
340 public function filterDay($request) 375 public function getLatest(): ?Bookmark
341 { 376 {
342 return $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_DAY, $request); 377 foreach ($this->search([], null, false, false, true) as $bookmark) {
378 return $bookmark;
379 }
380
381 return null;
343 } 382 }
344 383
345 /** 384 /**
346 * @inheritDoc 385 * @inheritDoc
347 */ 386 */
348 public function initialize() 387 public function initialize(): void
349 { 388 {
350 $initializer = new BookmarkInitializer($this); 389 $initializer = new BookmarkInitializer($this);
351 $initializer->initialize(); 390 $initializer->initialize();
391
392 if (true === $this->isLoggedIn) {
393 $this->save();
394 }
352 } 395 }
353 396
354 /** 397 /**
355 * Handles migration to the new database format (BookmarksArray). 398 * Handles migration to the new database format (BookmarksArray).
356 */ 399 */
357 protected function migrate() 400 protected function migrate(): void
358 { 401 {
359 $bookmarkDb = new LegacyLinkDB( 402 $bookmarkDb = new LegacyLinkDB(
360 $this->conf->get('resource.datastore'), 403 $this->conf->get('resource.datastore'),