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.php134
1 files changed, 76 insertions, 58 deletions
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php
index c9ec2609..6666a251 100644
--- a/application/bookmark/BookmarkFileService.php
+++ b/application/bookmark/BookmarkFileService.php
@@ -1,10 +1,12 @@
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;
9use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; 11use Shaarli\Bookmark\Exception\DatastoreNotInitializedException;
10use Shaarli\Bookmark\Exception\EmptyDataStoreException; 12use Shaarli\Bookmark\Exception\EmptyDataStoreException;
@@ -47,15 +49,19 @@ class BookmarkFileService implements BookmarkServiceInterface
47 /** @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. */
48 protected $isLoggedIn; 50 protected $isLoggedIn;
49 51
52 /** @var Mutex */
53 protected $mutex;
54
50 /** 55 /**
51 * @inheritDoc 56 * @inheritDoc
52 */ 57 */
53 public function __construct(ConfigManager $conf, History $history, $isLoggedIn) 58 public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
54 { 59 {
55 $this->conf = $conf; 60 $this->conf = $conf;
56 $this->history = $history; 61 $this->history = $history;
62 $this->mutex = $mutex;
57 $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn); 63 $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn);
58 $this->bookmarksIO = new BookmarkIO($this->conf); 64 $this->bookmarksIO = new BookmarkIO($this->conf, $this->mutex);
59 $this->isLoggedIn = $isLoggedIn; 65 $this->isLoggedIn = $isLoggedIn;
60 66
61 if (!$this->isLoggedIn && $this->conf->get('privacy.hide_public_links', false)) { 67 if (!$this->isLoggedIn && $this->conf->get('privacy.hide_public_links', false)) {
@@ -63,7 +69,7 @@ class BookmarkFileService implements BookmarkServiceInterface
63 } else { 69 } else {
64 try { 70 try {
65 $this->bookmarks = $this->bookmarksIO->read(); 71 $this->bookmarks = $this->bookmarksIO->read();
66 } catch (EmptyDataStoreException|DatastoreNotInitializedException $e) { 72 } catch (EmptyDataStoreException | DatastoreNotInitializedException $e) {
67 $this->bookmarks = new BookmarkArray(); 73 $this->bookmarks = new BookmarkArray();
68 74
69 if ($this->isLoggedIn) { 75 if ($this->isLoggedIn) {
@@ -79,25 +85,29 @@ class BookmarkFileService implements BookmarkServiceInterface
79 if (! $this->bookmarks instanceof BookmarkArray) { 85 if (! $this->bookmarks instanceof BookmarkArray) {
80 $this->migrate(); 86 $this->migrate();
81 exit( 87 exit(
82 'Your data store has been migrated, please reload the page.'. PHP_EOL . 88 'Your data store has been migrated, please reload the page.' . PHP_EOL .
83 'If this message keeps showing up, please delete data/updates.txt file.' 89 'If this message keeps showing up, please delete data/updates.txt file.'
84 ); 90 );
85 } 91 }
86 } 92 }
87 93
88 $this->bookmarkFilter = new BookmarkFilter($this->bookmarks); 94 $this->bookmarkFilter = new BookmarkFilter($this->bookmarks, $this->conf);
89 } 95 }
90 96
91 /** 97 /**
92 * @inheritDoc 98 * @inheritDoc
93 */ 99 */
94 public function findByHash($hash) 100 public function findByHash(string $hash, string $privateKey = null): Bookmark
95 { 101 {
96 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash); 102 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash);
97 // PHP 7.3 introduced array_key_first() to avoid this hack 103 // PHP 7.3 introduced array_key_first() to avoid this hack
98 $first = reset($bookmark); 104 $first = reset($bookmark);
99 if (! $this->isLoggedIn && $first->isPrivate()) { 105 if (
100 throw new Exception('Not authorized'); 106 !$this->isLoggedIn
107 && $first->isPrivate()
108 && (empty($privateKey) || $privateKey !== $first->getAdditionalContentEntry('private_key'))
109 ) {
110 throw new BookmarkNotFoundException();
101 } 111 }
102 112
103 return $first; 113 return $first;
@@ -106,7 +116,7 @@ class BookmarkFileService implements BookmarkServiceInterface
106 /** 116 /**
107 * @inheritDoc 117 * @inheritDoc
108 */ 118 */
109 public function findByUrl($url) 119 public function findByUrl(string $url): ?Bookmark
110 { 120 {
111 return $this->bookmarks->getByUrl($url); 121 return $this->bookmarks->getByUrl($url);
112 } 122 }
@@ -115,10 +125,10 @@ class BookmarkFileService implements BookmarkServiceInterface
115 * @inheritDoc 125 * @inheritDoc
116 */ 126 */
117 public function search( 127 public function search(
118 $request = [], 128 array $request = [],
119 $visibility = null, 129 string $visibility = null,
120 $caseSensitive = false, 130 bool $caseSensitive = false,
121 $untaggedOnly = false, 131 bool $untaggedOnly = false,
122 bool $ignoreSticky = false 132 bool $ignoreSticky = false
123 ) { 133 ) {
124 if ($visibility === null) { 134 if ($visibility === null) {
@@ -126,8 +136,8 @@ class BookmarkFileService implements BookmarkServiceInterface
126 } 136 }
127 137
128 // Filter bookmark database according to parameters. 138 // Filter bookmark database according to parameters.
129 $searchtags = isset($request['searchtags']) ? $request['searchtags'] : ''; 139 $searchTags = isset($request['searchtags']) ? $request['searchtags'] : '';
130 $searchterm = isset($request['searchterm']) ? $request['searchterm'] : ''; 140 $searchTerm = isset($request['searchterm']) ? $request['searchterm'] : '';
131 141
132 if ($ignoreSticky) { 142 if ($ignoreSticky) {
133 $this->bookmarks->reorder('DESC', true); 143 $this->bookmarks->reorder('DESC', true);
@@ -135,7 +145,7 @@ class BookmarkFileService implements BookmarkServiceInterface
135 145
136 return $this->bookmarkFilter->filter( 146 return $this->bookmarkFilter->filter(
137 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, 147 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
138 [$searchtags, $searchterm], 148 [$searchTags, $searchTerm],
139 $caseSensitive, 149 $caseSensitive,
140 $visibility, 150 $visibility,
141 $untaggedOnly 151 $untaggedOnly
@@ -145,7 +155,7 @@ class BookmarkFileService implements BookmarkServiceInterface
145 /** 155 /**
146 * @inheritDoc 156 * @inheritDoc
147 */ 157 */
148 public function get($id, $visibility = null) 158 public function get(int $id, string $visibility = null): Bookmark
149 { 159 {
150 if (! isset($this->bookmarks[$id])) { 160 if (! isset($this->bookmarks[$id])) {
151 throw new BookmarkNotFoundException(); 161 throw new BookmarkNotFoundException();
@@ -156,7 +166,8 @@ class BookmarkFileService implements BookmarkServiceInterface
156 } 166 }
157 167
158 $bookmark = $this->bookmarks[$id]; 168 $bookmark = $this->bookmarks[$id];
159 if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private') 169 if (
170 ($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
160 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public') 171 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
161 ) { 172 ) {
162 throw new Exception('Unauthorized'); 173 throw new Exception('Unauthorized');
@@ -168,20 +179,17 @@ class BookmarkFileService implements BookmarkServiceInterface
168 /** 179 /**
169 * @inheritDoc 180 * @inheritDoc
170 */ 181 */
171 public function set($bookmark, $save = true) 182 public function set(Bookmark $bookmark, bool $save = true): Bookmark
172 { 183 {
173 if (true !== $this->isLoggedIn) { 184 if (true !== $this->isLoggedIn) {
174 throw new Exception(t('You\'re not authorized to alter the datastore')); 185 throw new Exception(t('You\'re not authorized to alter the datastore'));
175 } 186 }
176 if (! $bookmark instanceof Bookmark) {
177 throw new Exception(t('Provided data is invalid'));
178 }
179 if (! isset($this->bookmarks[$bookmark->getId()])) { 187 if (! isset($this->bookmarks[$bookmark->getId()])) {
180 throw new BookmarkNotFoundException(); 188 throw new BookmarkNotFoundException();
181 } 189 }
182 $bookmark->validate(); 190 $bookmark->validate();
183 191
184 $bookmark->setUpdated(new \DateTime()); 192 $bookmark->setUpdated(new DateTime());
185 $this->bookmarks[$bookmark->getId()] = $bookmark; 193 $this->bookmarks[$bookmark->getId()] = $bookmark;
186 if ($save === true) { 194 if ($save === true) {
187 $this->save(); 195 $this->save();
@@ -193,15 +201,12 @@ class BookmarkFileService implements BookmarkServiceInterface
193 /** 201 /**
194 * @inheritDoc 202 * @inheritDoc
195 */ 203 */
196 public function add($bookmark, $save = true) 204 public function add(Bookmark $bookmark, bool $save = true): Bookmark
197 { 205 {
198 if (true !== $this->isLoggedIn) { 206 if (true !== $this->isLoggedIn) {
199 throw new Exception(t('You\'re not authorized to alter the datastore')); 207 throw new Exception(t('You\'re not authorized to alter the datastore'));
200 } 208 }
201 if (! $bookmark instanceof Bookmark) { 209 if (!empty($bookmark->getId())) {
202 throw new Exception(t('Provided data is invalid'));
203 }
204 if (! empty($bookmark->getId())) {
205 throw new Exception(t('This bookmarks already exists')); 210 throw new Exception(t('This bookmarks already exists'));
206 } 211 }
207 $bookmark->setId($this->bookmarks->getNextId()); 212 $bookmark->setId($this->bookmarks->getNextId());
@@ -218,14 +223,11 @@ class BookmarkFileService implements BookmarkServiceInterface
218 /** 223 /**
219 * @inheritDoc 224 * @inheritDoc
220 */ 225 */
221 public function addOrSet($bookmark, $save = true) 226 public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark
222 { 227 {
223 if (true !== $this->isLoggedIn) { 228 if (true !== $this->isLoggedIn) {
224 throw new Exception(t('You\'re not authorized to alter the datastore')); 229 throw new Exception(t('You\'re not authorized to alter the datastore'));
225 } 230 }
226 if (! $bookmark instanceof Bookmark) {
227 throw new Exception('Provided data is invalid');
228 }
229 if ($bookmark->getId() === null) { 231 if ($bookmark->getId() === null) {
230 return $this->add($bookmark, $save); 232 return $this->add($bookmark, $save);
231 } 233 }
@@ -235,14 +237,11 @@ class BookmarkFileService implements BookmarkServiceInterface
235 /** 237 /**
236 * @inheritDoc 238 * @inheritDoc
237 */ 239 */
238 public function remove($bookmark, $save = true) 240 public function remove(Bookmark $bookmark, bool $save = true): void
239 { 241 {
240 if (true !== $this->isLoggedIn) { 242 if (true !== $this->isLoggedIn) {
241 throw new Exception(t('You\'re not authorized to alter the datastore')); 243 throw new Exception(t('You\'re not authorized to alter the datastore'));
242 } 244 }
243 if (! $bookmark instanceof Bookmark) {
244 throw new Exception(t('Provided data is invalid'));
245 }
246 if (! isset($this->bookmarks[$bookmark->getId()])) { 245 if (! isset($this->bookmarks[$bookmark->getId()])) {
247 throw new BookmarkNotFoundException(); 246 throw new BookmarkNotFoundException();
248 } 247 }
@@ -257,7 +256,7 @@ class BookmarkFileService implements BookmarkServiceInterface
257 /** 256 /**
258 * @inheritDoc 257 * @inheritDoc
259 */ 258 */
260 public function exists($id, $visibility = null) 259 public function exists(int $id, string $visibility = null): bool
261 { 260 {
262 if (! isset($this->bookmarks[$id])) { 261 if (! isset($this->bookmarks[$id])) {
263 return false; 262 return false;
@@ -268,7 +267,8 @@ class BookmarkFileService implements BookmarkServiceInterface
268 } 267 }
269 268
270 $bookmark = $this->bookmarks[$id]; 269 $bookmark = $this->bookmarks[$id];
271 if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private') 270 if (
271 ($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
272 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public') 272 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
273 ) { 273 ) {
274 return false; 274 return false;
@@ -280,7 +280,7 @@ class BookmarkFileService implements BookmarkServiceInterface
280 /** 280 /**
281 * @inheritDoc 281 * @inheritDoc
282 */ 282 */
283 public function count($visibility = null) 283 public function count(string $visibility = null): int
284 { 284 {
285 return count($this->search([], $visibility)); 285 return count($this->search([], $visibility));
286 } 286 }
@@ -288,7 +288,7 @@ class BookmarkFileService implements BookmarkServiceInterface
288 /** 288 /**
289 * @inheritDoc 289 * @inheritDoc
290 */ 290 */
291 public function save() 291 public function save(): void
292 { 292 {
293 if (true !== $this->isLoggedIn) { 293 if (true !== $this->isLoggedIn) {
294 // TODO: raise an Exception instead 294 // TODO: raise an Exception instead
@@ -303,14 +303,15 @@ class BookmarkFileService implements BookmarkServiceInterface
303 /** 303 /**
304 * @inheritDoc 304 * @inheritDoc
305 */ 305 */
306 public function bookmarksCountPerTag($filteringTags = [], $visibility = null) 306 public function bookmarksCountPerTag(array $filteringTags = [], string $visibility = null): array
307 { 307 {
308 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility); 308 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
309 $tags = []; 309 $tags = [];
310 $caseMapping = []; 310 $caseMapping = [];
311 foreach ($bookmarks as $bookmark) { 311 foreach ($bookmarks as $bookmark) {
312 foreach ($bookmark->getTags() as $tag) { 312 foreach ($bookmark->getTags() as $tag) {
313 if (empty($tag) 313 if (
314 empty($tag)
314 || (! $this->isLoggedIn && startsWith($tag, '.')) 315 || (! $this->isLoggedIn && startsWith($tag, '.'))
315 || $tag === BookmarkMarkdownFormatter::NO_MD_TAG 316 || $tag === BookmarkMarkdownFormatter::NO_MD_TAG
316 || in_array($tag, $filteringTags, true) 317 || in_array($tag, $filteringTags, true)
@@ -339,38 +340,55 @@ class BookmarkFileService implements BookmarkServiceInterface
339 $keys = array_keys($tags); 340 $keys = array_keys($tags);
340 $tmpTags = array_combine($keys, $keys); 341 $tmpTags = array_combine($keys, $keys);
341 array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags); 342 array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
343
342 return $tags; 344 return $tags;
343 } 345 }
344 346
345 /** 347 /**
346 * @inheritDoc 348 * @inheritDoc
347 */ 349 */
348 public function days() 350 public function findByDate(
349 { 351 \DateTimeInterface $from,
350 $bookmarkDays = []; 352 \DateTimeInterface $to,
351 foreach ($this->search() as $bookmark) { 353 ?\DateTimeInterface &$previous,
352 $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0; 354 ?\DateTimeInterface &$next
355 ): array {
356 $out = [];
357 $previous = null;
358 $next = null;
359
360 foreach ($this->search([], null, false, false, true) as $bookmark) {
361 if ($to < $bookmark->getCreated()) {
362 $next = $bookmark->getCreated();
363 } elseif ($from < $bookmark->getCreated() && $to > $bookmark->getCreated()) {
364 $out[] = $bookmark;
365 } else {
366 if ($previous !== null) {
367 break;
368 }
369 $previous = $bookmark->getCreated();
370 }
353 } 371 }
354 $bookmarkDays = array_keys($bookmarkDays);
355 sort($bookmarkDays);
356 372
357 return $bookmarkDays; 373 return $out;
358 } 374 }
359 375
360 /** 376 /**
361 * @inheritDoc 377 * @inheritDoc
362 */ 378 */
363 public function filterDay($request) 379 public function getLatest(): ?Bookmark
364 { 380 {
365 $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC; 381 foreach ($this->search([], null, false, false, true) as $bookmark) {
382 return $bookmark;
383 }
366 384
367 return $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_DAY, $request, false, $visibility); 385 return null;
368 } 386 }
369 387
370 /** 388 /**
371 * @inheritDoc 389 * @inheritDoc
372 */ 390 */
373 public function initialize() 391 public function initialize(): void
374 { 392 {
375 $initializer = new BookmarkInitializer($this); 393 $initializer = new BookmarkInitializer($this);
376 $initializer->initialize(); 394 $initializer->initialize();
@@ -383,7 +401,7 @@ class BookmarkFileService implements BookmarkServiceInterface
383 /** 401 /**
384 * Handles migration to the new database format (BookmarksArray). 402 * Handles migration to the new database format (BookmarksArray).
385 */ 403 */
386 protected function migrate() 404 protected function migrate(): void
387 { 405 {
388 $bookmarkDb = new LegacyLinkDB( 406 $bookmarkDb = new LegacyLinkDB(
389 $this->conf->get('resource.datastore'), 407 $this->conf->get('resource.datastore'),
@@ -391,14 +409,14 @@ class BookmarkFileService implements BookmarkServiceInterface
391 false 409 false
392 ); 410 );
393 $updater = new LegacyUpdater( 411 $updater = new LegacyUpdater(
394 UpdaterUtils::read_updates_file($this->conf->get('resource.updates')), 412 UpdaterUtils::readUpdatesFile($this->conf->get('resource.updates')),
395 $bookmarkDb, 413 $bookmarkDb,
396 $this->conf, 414 $this->conf,
397 true 415 true
398 ); 416 );
399 $newUpdates = $updater->update(); 417 $newUpdates = $updater->update();
400 if (! empty($newUpdates)) { 418 if (! empty($newUpdates)) {
401 UpdaterUtils::write_updates_file( 419 UpdaterUtils::writeUpdatesFile(
402 $this->conf->get('resource.updates'), 420 $this->conf->get('resource.updates'),
403 $updater->getDoneUpdates() 421 $updater->getDoneUpdates()
404 ); 422 );