4 namespace Shaarli\Bookmark
;
8 use Shaarli\Bookmark\Exception\BookmarkNotFoundException
;
9 use Shaarli\Bookmark\Exception\EmptyDataStoreException
;
10 use Shaarli\Config\ConfigManager
;
11 use Shaarli\Formatter\BookmarkMarkdownFormatter
;
13 use Shaarli\Legacy\LegacyLinkDB
;
14 use Shaarli\Legacy\LegacyUpdater
;
15 use Shaarli\Render\PageCacheManager
;
16 use Shaarli\Updater\UpdaterUtils
;
19 * Class BookmarksService
21 * This is the entry point to manipulate the bookmark DB.
22 * It manipulates loads links from a file data store containing all bookmarks.
24 * It also triggers the legacy format (bookmarks as arrays) migration.
26 class BookmarkFileService
implements BookmarkServiceInterface
28 /** @var Bookmark[] instance */
31 /** @var BookmarkIO instance */
32 protected $bookmarksIO;
34 /** @var BookmarkFilter */
35 protected $bookmarkFilter;
37 /** @var ConfigManager instance */
40 /** @var History instance */
43 /** @var PageCacheManager instance */
44 protected $pageCacheManager;
46 /** @var bool true for logged in users. Default value to retrieve private bookmarks. */
47 protected $isLoggedIn;
49 /** @var bool Allow datastore alteration from not logged in users. */
50 protected $anonymousPermission = false;
55 public function __construct(ConfigManager
$conf, History
$history, $isLoggedIn)
58 $this->history
= $history;
59 $this->pageCacheManager
= new PageCacheManager($this->conf
->get('resource.page_cache'), $isLoggedIn);
60 $this->bookmarksIO
= new BookmarkIO($this->conf
);
61 $this->isLoggedIn
= $isLoggedIn;
63 if (!$this->isLoggedIn
&& $this->conf
->get('privacy.hide_public_links', false)) {
64 $this->bookmarks
= [];
67 $this->bookmarks
= $this->bookmarksIO
->read();
68 } catch (EmptyDataStoreException
$e) {
69 $this->bookmarks
= new BookmarkArray();
70 if ($this->isLoggedIn
) {
75 if (! $this->bookmarks
instanceof BookmarkArray
) {
78 'Your data store has been migrated, please reload the page.'. PHP_EOL
.
79 'If this message keeps showing up, please delete data/updates.txt file.'
84 $this->bookmarkFilter
= new BookmarkFilter($this->bookmarks
);
90 public function findByHash($hash)
92 $bookmark = $this->bookmarkFilter
->filter(BookmarkFilter
::$FILTER_HASH, $hash);
93 // PHP 7.3 introduced array_key_first() to avoid this hack
94 $first = reset($bookmark);
95 if (! $this->isLoggedIn
&& $first->isPrivate()) {
96 throw new Exception('Not authorized');
105 public function findByUrl($url)
107 return $this->bookmarks
->getByUrl($url);
113 public function search($request = [], $visibility = null, $caseSensitive = false, $untaggedOnly = false)
115 if ($visibility === null) {
116 $visibility = $this->isLoggedIn
? BookmarkFilter
::$ALL : BookmarkFilter
::$PUBLIC;
119 // Filter bookmark database according to parameters.
120 $searchtags = isset($request['searchtags']) ? $request['searchtags'] : '';
121 $searchterm = isset($request['searchterm']) ? $request['searchterm'] : '';
123 return $this->bookmarkFilter
->filter(
124 BookmarkFilter
::$FILTER_TAG | BookmarkFilter
::$FILTER_TEXT,
125 [$searchtags, $searchterm],
135 public function get($id, $visibility = null)
137 if (! isset($this->bookmarks
[$id])) {
138 throw new BookmarkNotFoundException();
141 if ($visibility === null) {
142 $visibility = $this->isLoggedIn
? BookmarkFilter
::$ALL : BookmarkFilter
::$PUBLIC;
145 $bookmark = $this->bookmarks
[$id];
146 if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
147 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
149 throw new Exception('Unauthorized');
158 public function set($bookmark, $save = true)
160 if (true !== $this->isLoggedIn
&& true !== $this->anonymousPermission
) {
161 throw new Exception(t('You\'re not authorized to alter the datastore'));
163 if (! $bookmark instanceof Bookmark
) {
164 throw new Exception(t('Provided data is invalid'));
166 if (! isset($this->bookmarks
[$bookmark->getId()])) {
167 throw new BookmarkNotFoundException();
169 $bookmark->validate();
171 $bookmark->setUpdated(new \
DateTime());
172 $this->bookmarks
[$bookmark->getId()] = $bookmark;
173 if ($save === true) {
175 $this->history
->updateLink($bookmark);
177 return $this->bookmarks
[$bookmark->getId()];
183 public function add($bookmark, $save = true)
185 if (true !== $this->isLoggedIn
&& true !== $this->anonymousPermission
) {
186 throw new Exception(t('You\'re not authorized to alter the datastore'));
188 if (! $bookmark instanceof Bookmark
) {
189 throw new Exception(t('Provided data is invalid'));
191 if (! empty($bookmark->getId())) {
192 throw new Exception(t('This bookmarks already exists'));
194 $bookmark->setId($this->bookmarks
->getNextId());
195 $bookmark->validate();
197 $this->bookmarks
[$bookmark->getId()] = $bookmark;
198 if ($save === true) {
200 $this->history
->addLink($bookmark);
202 return $this->bookmarks
[$bookmark->getId()];
208 public function addOrSet($bookmark, $save = true)
210 if (true !== $this->isLoggedIn
&& true !== $this->anonymousPermission
) {
211 throw new Exception(t('You\'re not authorized to alter the datastore'));
213 if (! $bookmark instanceof Bookmark
) {
214 throw new Exception('Provided data is invalid');
216 if ($bookmark->getId() === null) {
217 return $this->add($bookmark, $save);
219 return $this->set($bookmark, $save);
225 public function remove($bookmark, $save = true)
227 if (true !== $this->isLoggedIn
&& true !== $this->anonymousPermission
) {
228 throw new Exception(t('You\'re not authorized to alter the datastore'));
230 if (! $bookmark instanceof Bookmark
) {
231 throw new Exception(t('Provided data is invalid'));
233 if (! isset($this->bookmarks
[$bookmark->getId()])) {
234 throw new BookmarkNotFoundException();
237 unset($this->bookmarks
[$bookmark->getId()]);
238 if ($save === true) {
240 $this->history
->deleteLink($bookmark);
247 public function exists($id, $visibility = null)
249 if (! isset($this->bookmarks
[$id])) {
253 if ($visibility === null) {
254 $visibility = $this->isLoggedIn
? 'all' : 'public';
257 $bookmark = $this->bookmarks
[$id];
258 if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
259 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
270 public function count($visibility = null)
272 return count($this->search([], $visibility));
278 public function save()
280 if (true !== $this->isLoggedIn
&& true !== $this->anonymousPermission
) {
281 // TODO: raise an Exception instead
282 die('You are not authorized to change the database.');
285 $this->bookmarks
->reorder();
286 $this->bookmarksIO
->write($this->bookmarks
);
287 $this->pageCacheManager
->invalidateCaches();
293 public function bookmarksCountPerTag($filteringTags = [], $visibility = null)
295 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
298 foreach ($bookmarks as $bookmark) {
299 foreach ($bookmark->getTags() as $tag) {
301 || (! $this->isLoggedIn
&& startsWith($tag, '.'))
302 || $tag === BookmarkMarkdownFormatter
::NO_MD_TAG
303 || in_array($tag, $filteringTags, true)
308 // The first case found will be displayed.
309 if (!isset($caseMapping[strtolower($tag)])) {
310 $caseMapping[strtolower($tag)] = $tag;
311 $tags[$caseMapping[strtolower($tag)]] = 0;
313 $tags[$caseMapping[strtolower($tag)]]++
;
318 * Formerly used arsort(), which doesn't define the sort behaviour for equal values.
319 * Also, this function doesn't produce the same result between PHP 5.6 and 7.
321 * So we now use array_multisort() to sort tags by DESC occurrences,
322 * then ASC alphabetically for equal values.
324 * @see https://github.com/shaarli/Shaarli/issues/1142
326 $keys = array_keys($tags);
327 $tmpTags = array_combine($keys, $keys);
328 array_multisort($tags, SORT_DESC
, $tmpTags, SORT_ASC
, $tags);
335 public function days()
338 foreach ($this->search() as $bookmark) {
339 $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0;
341 $bookmarkDays = array_keys($bookmarkDays);
344 return $bookmarkDays;
350 public function filterDay($request)
352 return $this->bookmarkFilter
->filter(BookmarkFilter
::$FILTER_DAY, $request);
358 public function initialize()
360 $initializer = new BookmarkInitializer($this);
361 $initializer->initialize();
364 public function enableAnonymousPermission(): void
366 $this->anonymousPermission
= true;
369 public function disableAnonymousPermission(): void
371 $this->anonymousPermission
= false;
375 * Handles migration to the new database format (BookmarksArray).
377 protected function migrate()
379 $bookmarkDb = new LegacyLinkDB(
380 $this->conf
->get('resource.datastore'),
384 $updater = new LegacyUpdater(
385 UpdaterUtils
::read_updates_file($this->conf
->get('resource.updates')),
390 $newUpdates = $updater->update();
391 if (! empty($newUpdates)) {
392 UpdaterUtils
::write_updates_file(
393 $this->conf
->get('resource.updates'),
394 $updater->getDoneUpdates()