<?php
+declare(strict_types=1);
namespace Shaarli\Bookmark;
-
+use DateTime;
use Exception;
+use malkusch\lock\mutex\Mutex;
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
+use Shaarli\Bookmark\Exception\DatastoreNotInitializedException;
use Shaarli\Bookmark\Exception\EmptyDataStoreException;
use Shaarli\Config\ConfigManager;
+use Shaarli\Formatter\BookmarkMarkdownFormatter;
use Shaarli\History;
use Shaarli\Legacy\LegacyLinkDB;
use Shaarli\Legacy\LegacyUpdater;
+use Shaarli\Render\PageCacheManager;
use Shaarli\Updater\UpdaterUtils;
/**
/** @var History instance */
protected $history;
+ /** @var PageCacheManager instance */
+ protected $pageCacheManager;
+
/** @var bool true for logged in users. Default value to retrieve private bookmarks. */
protected $isLoggedIn;
+ /** @var Mutex */
+ protected $mutex;
+
/**
* @inheritDoc
*/
- public function __construct(ConfigManager $conf, History $history, $isLoggedIn)
+ public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
{
$this->conf = $conf;
$this->history = $history;
- $this->bookmarksIO = new BookmarkIO($this->conf);
+ $this->mutex = $mutex;
+ $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn);
+ $this->bookmarksIO = new BookmarkIO($this->conf, $this->mutex);
$this->isLoggedIn = $isLoggedIn;
if (!$this->isLoggedIn && $this->conf->get('privacy.hide_public_links', false)) {
} else {
try {
$this->bookmarks = $this->bookmarksIO->read();
- } catch (EmptyDataStoreException $e) {
+ } catch (EmptyDataStoreException | DatastoreNotInitializedException $e) {
$this->bookmarks = new BookmarkArray();
- if ($isLoggedIn) {
- $this->save();
+
+ if ($this->isLoggedIn) {
+ // Datastore file does not exists, we initialize it with default bookmarks.
+ if ($e instanceof DatastoreNotInitializedException) {
+ $this->initialize();
+ } else {
+ $this->save();
+ }
}
}
if (! $this->bookmarks instanceof BookmarkArray) {
$this->migrate();
exit(
- 'Your data store has been migrated, please reload the page.'. PHP_EOL .
+ 'Your data store has been migrated, please reload the page.' . PHP_EOL .
'If this message keeps showing up, please delete data/updates.txt file.'
);
}
}
- $this->bookmarkFilter = new BookmarkFilter($this->bookmarks);
+ $this->bookmarkFilter = new BookmarkFilter($this->bookmarks, $this->conf);
}
/**
* @inheritDoc
*/
- public function findByHash($hash)
+ public function findByHash(string $hash, string $privateKey = null): Bookmark
{
$bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash);
// PHP 7.3 introduced array_key_first() to avoid this hack
$first = reset($bookmark);
- if (! $this->isLoggedIn && $first->isPrivate()) {
- throw new Exception('Not authorized');
+ if (
+ !$this->isLoggedIn
+ && $first->isPrivate()
+ && (empty($privateKey) || $privateKey !== $first->getAdditionalContentEntry('private_key'))
+ ) {
+ throw new BookmarkNotFoundException();
}
- return $bookmark;
+ return $first;
}
/**
* @inheritDoc
*/
- public function findByUrl($url)
+ public function findByUrl(string $url): ?Bookmark
{
return $this->bookmarks->getByUrl($url);
}
/**
* @inheritDoc
*/
- public function search($request = [], $visibility = null, $caseSensitive = false, $untaggedOnly = false)
- {
+ public function search(
+ array $request = [],
+ string $visibility = null,
+ bool $caseSensitive = false,
+ bool $untaggedOnly = false,
+ bool $ignoreSticky = false
+ ) {
if ($visibility === null) {
$visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
}
// Filter bookmark database according to parameters.
- $searchtags = isset($request['searchtags']) ? $request['searchtags'] : '';
- $searchterm = isset($request['searchterm']) ? $request['searchterm'] : '';
+ $searchTags = isset($request['searchtags']) ? $request['searchtags'] : '';
+ $searchTerm = isset($request['searchterm']) ? $request['searchterm'] : '';
+
+ if ($ignoreSticky) {
+ $this->bookmarks->reorder('DESC', true);
+ }
return $this->bookmarkFilter->filter(
BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
- [$searchtags, $searchterm],
+ [$searchTags, $searchTerm],
$caseSensitive,
$visibility,
$untaggedOnly
/**
* @inheritDoc
*/
- public function get($id, $visibility = null)
+ public function get(int $id, string $visibility = null): Bookmark
{
if (! isset($this->bookmarks[$id])) {
throw new BookmarkNotFoundException();
}
if ($visibility === null) {
- $visibility = $this->isLoggedIn ? 'all' : 'public';
+ $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
}
$bookmark = $this->bookmarks[$id];
- if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
+ if (
+ ($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
|| (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
) {
throw new Exception('Unauthorized');
/**
* @inheritDoc
*/
- public function set($bookmark, $save = true)
+ public function set(Bookmark $bookmark, bool $save = true): Bookmark
{
- if ($this->isLoggedIn !== true) {
+ if (true !== $this->isLoggedIn) {
throw new Exception(t('You\'re not authorized to alter the datastore'));
}
- if (! $bookmark instanceof Bookmark) {
- throw new Exception(t('Provided data is invalid'));
- }
if (! isset($this->bookmarks[$bookmark->getId()])) {
throw new BookmarkNotFoundException();
}
$bookmark->validate();
- $bookmark->setUpdated(new \DateTime());
+ $bookmark->setUpdated(new DateTime());
$this->bookmarks[$bookmark->getId()] = $bookmark;
if ($save === true) {
$this->save();
/**
* @inheritDoc
*/
- public function add($bookmark, $save = true)
+ public function add(Bookmark $bookmark, bool $save = true): Bookmark
{
- if ($this->isLoggedIn !== true) {
+ if (true !== $this->isLoggedIn) {
throw new Exception(t('You\'re not authorized to alter the datastore'));
}
- if (! $bookmark instanceof Bookmark) {
- throw new Exception(t('Provided data is invalid'));
- }
- if (! empty($bookmark->getId())) {
+ if (!empty($bookmark->getId())) {
throw new Exception(t('This bookmarks already exists'));
}
$bookmark->setId($this->bookmarks->getNextId());
/**
* @inheritDoc
*/
- public function addOrSet($bookmark, $save = true)
+ public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark
{
- if ($this->isLoggedIn !== true) {
+ if (true !== $this->isLoggedIn) {
throw new Exception(t('You\'re not authorized to alter the datastore'));
}
- if (! $bookmark instanceof Bookmark) {
- throw new Exception('Provided data is invalid');
- }
if ($bookmark->getId() === null) {
return $this->add($bookmark, $save);
}
/**
* @inheritDoc
*/
- public function remove($bookmark, $save = true)
+ public function remove(Bookmark $bookmark, bool $save = true): void
{
- if ($this->isLoggedIn !== true) {
+ if (true !== $this->isLoggedIn) {
throw new Exception(t('You\'re not authorized to alter the datastore'));
}
- if (! $bookmark instanceof Bookmark) {
- throw new Exception(t('Provided data is invalid'));
- }
if (! isset($this->bookmarks[$bookmark->getId()])) {
throw new BookmarkNotFoundException();
}
/**
* @inheritDoc
*/
- public function exists($id, $visibility = null)
+ public function exists(int $id, string $visibility = null): bool
{
if (! isset($this->bookmarks[$id])) {
return false;
}
$bookmark = $this->bookmarks[$id];
- if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
+ if (
+ ($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
|| (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
) {
return false;
/**
* @inheritDoc
*/
- public function count($visibility = null)
+ public function count(string $visibility = null): int
{
return count($this->search([], $visibility));
}
/**
* @inheritDoc
*/
- public function save()
+ public function save(): void
{
- if (!$this->isLoggedIn) {
+ if (true !== $this->isLoggedIn) {
// TODO: raise an Exception instead
die('You are not authorized to change the database.');
}
+
$this->bookmarks->reorder();
$this->bookmarksIO->write($this->bookmarks);
- invalidateCaches($this->conf->get('resource.page_cache'));
+ $this->pageCacheManager->invalidateCaches();
}
/**
* @inheritDoc
*/
- public function bookmarksCountPerTag($filteringTags = [], $visibility = null)
+ public function bookmarksCountPerTag(array $filteringTags = [], string $visibility = null): array
{
$bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
$tags = [];
$caseMapping = [];
foreach ($bookmarks as $bookmark) {
foreach ($bookmark->getTags() as $tag) {
- if (empty($tag) || (! $this->isLoggedIn && startsWith($tag, '.'))) {
+ if (
+ empty($tag)
+ || (! $this->isLoggedIn && startsWith($tag, '.'))
+ || $tag === BookmarkMarkdownFormatter::NO_MD_TAG
+ || in_array($tag, $filteringTags, true)
+ ) {
continue;
}
+
// The first case found will be displayed.
if (!isset($caseMapping[strtolower($tag)])) {
$caseMapping[strtolower($tag)] = $tag;
$keys = array_keys($tags);
$tmpTags = array_combine($keys, $keys);
array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
+
return $tags;
}
/**
* @inheritDoc
*/
- public function days()
- {
- $bookmarkDays = [];
- foreach ($this->search() as $bookmark) {
- $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0;
+ public function findByDate(
+ \DateTimeInterface $from,
+ \DateTimeInterface $to,
+ ?\DateTimeInterface &$previous,
+ ?\DateTimeInterface &$next
+ ): array {
+ $out = [];
+ $previous = null;
+ $next = null;
+
+ foreach ($this->search([], null, false, false, true) as $bookmark) {
+ if ($to < $bookmark->getCreated()) {
+ $next = $bookmark->getCreated();
+ } elseif ($from < $bookmark->getCreated() && $to > $bookmark->getCreated()) {
+ $out[] = $bookmark;
+ } else {
+ if ($previous !== null) {
+ break;
+ }
+ $previous = $bookmark->getCreated();
+ }
}
- $bookmarkDays = array_keys($bookmarkDays);
- sort($bookmarkDays);
- return $bookmarkDays;
+ return $out;
}
/**
* @inheritDoc
*/
- public function filterDay($request)
+ public function getLatest(): ?Bookmark
{
- return $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_DAY, $request);
+ foreach ($this->search([], null, false, false, true) as $bookmark) {
+ return $bookmark;
+ }
+
+ return null;
}
/**
* @inheritDoc
*/
- public function initialize()
+ public function initialize(): void
{
$initializer = new BookmarkInitializer($this);
$initializer->initialize();
+
+ if (true === $this->isLoggedIn) {
+ $this->save();
+ }
}
/**
* Handles migration to the new database format (BookmarksArray).
*/
- protected function migrate()
+ protected function migrate(): void
{
$bookmarkDb = new LegacyLinkDB(
$this->conf->get('resource.datastore'),
false
);
$updater = new LegacyUpdater(
- UpdaterUtils::read_updates_file($this->conf->get('resource.updates')),
+ UpdaterUtils::readUpdatesFile($this->conf->get('resource.updates')),
$bookmarkDb,
$this->conf,
true
);
$newUpdates = $updater->update();
if (! empty($newUpdates)) {
- UpdaterUtils::write_updates_file(
+ UpdaterUtils::writeUpdatesFile(
$this->conf->get('resource.updates'),
$updater->getDoneUpdates()
);