4 namespace Shaarli\Bookmark
;
8 use Shaarli\Bookmark\Exception\BookmarkNotFoundException
;
9 use Shaarli\Bookmark\Exception\EmptyDataStoreException
;
10 use Shaarli\Config\ConfigManager
;
12 use Shaarli\Legacy\LegacyLinkDB
;
13 use Shaarli\Legacy\LegacyUpdater
;
14 use Shaarli\Updater\UpdaterUtils
;
17 * Class BookmarksService
19 * This is the entry point to manipulate the bookmark DB.
20 * It manipulates loads links from a file data store containing all bookmarks.
22 * It also triggers the legacy format (bookmarks as arrays) migration.
24 class BookmarkFileService
implements BookmarkServiceInterface
26 /** @var Bookmark[] instance */
29 /** @var BookmarkIO instance */
30 protected $bookmarksIO;
32 /** @var BookmarkFilter */
33 protected $bookmarkFilter;
35 /** @var ConfigManager instance */
38 /** @var History instance */
41 /** @var bool true for logged in users. Default value to retrieve private bookmarks. */
42 protected $isLoggedIn;
47 public function __construct(ConfigManager
$conf, History
$history, $isLoggedIn)
50 $this->history
= $history;
51 $this->bookmarksIO
= new BookmarkIO($this->conf
);
52 $this->isLoggedIn
= $isLoggedIn;
54 if (!$this->isLoggedIn
&& $this->conf
->get('privacy.hide_public_links', false)) {
55 $this->bookmarks
= [];
58 $this->bookmarks
= $this->bookmarksIO
->read();
59 } catch (EmptyDataStoreException
$e) {
60 $this->bookmarks
= new BookmarkArray();
66 if (! $this->bookmarks
instanceof BookmarkArray
) {
69 'Your data store has been migrated, please reload the page.'. PHP_EOL
.
70 'If this message keeps showing up, please delete data/updates.txt file.'
75 $this->bookmarkFilter
= new BookmarkFilter($this->bookmarks
);
81 public function findByHash($hash)
83 $bookmark = $this->bookmarkFilter
->filter(BookmarkFilter
::$FILTER_HASH, $hash);
84 // PHP 7.3 introduced array_key_first() to avoid this hack
85 $first = reset($bookmark);
86 if (! $this->isLoggedIn
&& $first->isPrivate()) {
87 throw new Exception('Not authorized');
96 public function findByUrl($url)
98 return $this->bookmarks
->getByUrl($url);
104 public function search($request = [], $visibility = null, $caseSensitive = false, $untaggedOnly = false)
106 if ($visibility === null) {
107 $visibility = $this->isLoggedIn
? BookmarkFilter
::$ALL : BookmarkFilter
::$PUBLIC;
110 // Filter bookmark database according to parameters.
111 $searchtags = isset($request['searchtags']) ? $request['searchtags'] : '';
112 $searchterm = isset($request['searchterm']) ? $request['searchterm'] : '';
114 return $this->bookmarkFilter
->filter(
115 BookmarkFilter
::$FILTER_TAG | BookmarkFilter
::$FILTER_TEXT,
116 [$searchtags, $searchterm],
126 public function get($id, $visibility = null)
128 if (! isset($this->bookmarks
[$id])) {
129 throw new BookmarkNotFoundException();
132 if ($visibility === null) {
133 $visibility = $this->isLoggedIn
? 'all' : 'public';
136 $bookmark = $this->bookmarks
[$id];
137 if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
138 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
140 throw new Exception('Unauthorized');
149 public function set($bookmark, $save = true)
151 if ($this->isLoggedIn
!== true) {
152 throw new Exception(t('You\'re not authorized to alter the datastore'));
154 if (! $bookmark instanceof Bookmark
) {
155 throw new Exception(t('Provided data is invalid'));
157 if (! isset($this->bookmarks
[$bookmark->getId()])) {
158 throw new BookmarkNotFoundException();
160 $bookmark->validate();
162 $bookmark->setUpdated(new \
DateTime());
163 $this->bookmarks
[$bookmark->getId()] = $bookmark;
164 if ($save === true) {
166 $this->history
->updateLink($bookmark);
168 return $this->bookmarks
[$bookmark->getId()];
174 public function add($bookmark, $save = true)
176 if ($this->isLoggedIn
!== true) {
177 throw new Exception(t('You\'re not authorized to alter the datastore'));
179 if (! $bookmark instanceof Bookmark
) {
180 throw new Exception(t('Provided data is invalid'));
182 if (! empty($bookmark->getId())) {
183 throw new Exception(t('This bookmarks already exists'));
185 $bookmark->setId($this->bookmarks
->getNextId());
186 $bookmark->validate();
188 $this->bookmarks
[$bookmark->getId()] = $bookmark;
189 if ($save === true) {
191 $this->history
->addLink($bookmark);
193 return $this->bookmarks
[$bookmark->getId()];
199 public function addOrSet($bookmark, $save = true)
201 if ($this->isLoggedIn
!== true) {
202 throw new Exception(t('You\'re not authorized to alter the datastore'));
204 if (! $bookmark instanceof Bookmark
) {
205 throw new Exception('Provided data is invalid');
207 if ($bookmark->getId() === null) {
208 return $this->add($bookmark, $save);
210 return $this->set($bookmark, $save);
216 public function remove($bookmark, $save = true)
218 if ($this->isLoggedIn
!== true) {
219 throw new Exception(t('You\'re not authorized to alter the datastore'));
221 if (! $bookmark instanceof Bookmark
) {
222 throw new Exception(t('Provided data is invalid'));
224 if (! isset($this->bookmarks
[$bookmark->getId()])) {
225 throw new BookmarkNotFoundException();
228 unset($this->bookmarks
[$bookmark->getId()]);
229 if ($save === true) {
231 $this->history
->deleteLink($bookmark);
238 public function exists($id, $visibility = null)
240 if (! isset($this->bookmarks
[$id])) {
244 if ($visibility === null) {
245 $visibility = $this->isLoggedIn
? 'all' : 'public';
248 $bookmark = $this->bookmarks
[$id];
249 if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private')
250 || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public')
261 public function count($visibility = null)
263 return count($this->search([], $visibility));
269 public function save()
271 if (!$this->isLoggedIn
) {
272 // TODO: raise an Exception instead
273 die('You are not authorized to change the database.');
275 $this->bookmarks
->reorder();
276 $this->bookmarksIO
->write($this->bookmarks
);
277 invalidateCaches($this->conf
->get('resource.page_cache'));
283 public function bookmarksCountPerTag($filteringTags = [], $visibility = null)
285 $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
288 foreach ($bookmarks as $bookmark) {
289 foreach ($bookmark->getTags() as $tag) {
290 if (empty($tag) || (! $this->isLoggedIn
&& startsWith($tag, '.'))) {
293 // The first case found will be displayed.
294 if (!isset($caseMapping[strtolower($tag)])) {
295 $caseMapping[strtolower($tag)] = $tag;
296 $tags[$caseMapping[strtolower($tag)]] = 0;
298 $tags[$caseMapping[strtolower($tag)]]++
;
303 * Formerly used arsort(), which doesn't define the sort behaviour for equal values.
304 * Also, this function doesn't produce the same result between PHP 5.6 and 7.
306 * So we now use array_multisort() to sort tags by DESC occurrences,
307 * then ASC alphabetically for equal values.
309 * @see https://github.com/shaarli/Shaarli/issues/1142
311 $keys = array_keys($tags);
312 $tmpTags = array_combine($keys, $keys);
313 array_multisort($tags, SORT_DESC
, $tmpTags, SORT_ASC
, $tags);
320 public function days()
323 foreach ($this->search() as $bookmark) {
324 $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0;
326 $bookmarkDays = array_keys($bookmarkDays);
329 return $bookmarkDays;
335 public function filterDay($request)
337 return $this->bookmarkFilter
->filter(BookmarkFilter
::$FILTER_DAY, $request);
343 public function initialize()
345 $initializer = new BookmarkInitializer($this);
346 $initializer->initialize();
350 * Handles migration to the new database format (BookmarksArray).
352 protected function migrate()
354 $bookmarkDb = new LegacyLinkDB(
355 $this->conf
->get('resource.datastore'),
359 $updater = new LegacyUpdater(
360 UpdaterUtils
::read_updates_file($this->conf
->get('resource.updates')),
365 $newUpdates = $updater->update();
366 if (! empty($newUpdates)) {
367 UpdaterUtils
::write_updates_file(
368 $this->conf
->get('resource.updates'),
369 $updater->getDoneUpdates()