Skip to content

Commit

Permalink
Handle pagination through BookmarkService
Browse files Browse the repository at this point in the history
Handle all search results through SearchResult object.
This is a required step toward implementing a BookmarkService based on SQL database.

Related to shaarli#953
  • Loading branch information
ArthurHoaro committed Jan 20, 2021
1 parent 055d97f commit 5bc3b6a
Show file tree
Hide file tree
Showing 19 changed files with 415 additions and 146 deletions.
38 changes: 19 additions & 19 deletions application/api/controllers/Links.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,13 @@ class Links extends ApiController
public function getLinks($request, $response)
{
$private = $request->getParam('visibility');
$bookmarks = $this->bookmarkService->search(
[
'searchtags' => $request->getParam('searchtags', ''),
'searchterm' => $request->getParam('searchterm', ''),
],
$private
);

// Return bookmarks from the {offset}th link, starting from 0.
$offset = $request->getParam('offset');
if (! empty($offset) && ! ctype_digit($offset)) {
throw new ApiBadParametersException('Invalid offset');
}
$offset = ! empty($offset) ? intval($offset) : 0;
if ($offset > count($bookmarks)) {
return $response->withJson([], 200, $this->jsonStyle);
}

// limit parameter is either a number of bookmarks or 'all' for everything.
$limit = $request->getParam('limit');
Expand All @@ -61,23 +51,33 @@ public function getLinks($request, $response)
} elseif (ctype_digit($limit)) {
$limit = intval($limit);
} elseif ($limit === 'all') {
$limit = count($bookmarks);
$limit = null;
} else {
throw new ApiBadParametersException('Invalid limit');
}

$searchResult = $this->bookmarkService->search(
[
'searchtags' => $request->getParam('searchtags', ''),
'searchterm' => $request->getParam('searchterm', ''),
],
$private,
false,
false,
false,
[
'limit' => $limit,
'offset' => $offset,
'allowOutOfBounds' => true,
]
);

// 'environment' is set by Slim and encapsulate $_SERVER.
$indexUrl = index_url($this->ci['environment']);

$out = [];
$index = 0;
foreach ($bookmarks as $bookmark) {
if (count($out) >= $limit) {
break;
}
if ($index++ >= $offset) {
$out[] = ApiUtils::formatLink($bookmark, $indexUrl);
}
foreach ($searchResult->getBookmarks() as $bookmark) {
$out[] = ApiUtils::formatLink($bookmark, $indexUrl);
}

return $response->withJson($out, 200, $this->jsonStyle);
Expand Down
8 changes: 4 additions & 4 deletions application/api/controllers/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ public function putTag($request, $response, $args)
throw new ApiBadParametersException('New tag name is required in the request body');
}

$bookmarks = $this->bookmarkService->search(
$searchResult = $this->bookmarkService->search(
['searchtags' => $args['tagName']],
BookmarkFilter::$ALL,
true
);
foreach ($bookmarks as $bookmark) {
foreach ($searchResult->getBookmarks() as $bookmark) {
$bookmark->renameTag($args['tagName'], $data['name']);
$this->bookmarkService->set($bookmark, false);
$this->history->updateLink($bookmark);
Expand Down Expand Up @@ -157,12 +157,12 @@ public function deleteTag($request, $response, $args)
throw new ApiTagNotFoundException();
}

$bookmarks = $this->bookmarkService->search(
$searchResult = $this->bookmarkService->search(
['searchtags' => $args['tagName']],
BookmarkFilter::$ALL,
true
);
foreach ($bookmarks as $bookmark) {
foreach ($searchResult->getBookmarks() as $bookmark) {
$bookmark->deleteTag($args['tagName']);
$this->bookmarkService->set($bookmark, false);
$this->history->updateLink($bookmark);
Expand Down
40 changes: 29 additions & 11 deletions application/bookmark/BookmarkFileService.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Shaarli\History;
use Shaarli\Legacy\LegacyLinkDB;
use Shaarli\Legacy\LegacyUpdater;
use Shaarli\Plugin\PluginManager;
use Shaarli\Render\PageCacheManager;
use Shaarli\Updater\UpdaterUtils;

Expand Down Expand Up @@ -52,11 +53,19 @@ class BookmarkFileService implements BookmarkServiceInterface
/** @var Mutex */
protected $mutex;

/** @var PluginManager */
protected $pluginManager;

/**
* @inheritDoc
*/
public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
{
public function __construct(
ConfigManager $conf,
PluginManager $pluginManager,
History $history,
Mutex $mutex,
bool $isLoggedIn
) {
$this->conf = $conf;
$this->history = $history;
$this->mutex = $mutex;
Expand Down Expand Up @@ -91,7 +100,8 @@ public function __construct(ConfigManager $conf, History $history, Mutex $mutex,
}
}

$this->bookmarkFilter = new BookmarkFilter($this->bookmarks, $this->conf);
$this->pluginManager = $pluginManager;
$this->bookmarkFilter = new BookmarkFilter($this->bookmarks, $this->conf, $this->pluginManager);
}

/**
Expand Down Expand Up @@ -129,8 +139,9 @@ public function search(
string $visibility = null,
bool $caseSensitive = false,
bool $untaggedOnly = false,
bool $ignoreSticky = false
) {
bool $ignoreSticky = false,
array $pagination = []
): SearchResult {
if ($visibility === null) {
$visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
}
Expand All @@ -143,13 +154,20 @@ public function search(
$this->bookmarks->reorder('DESC', true);
}

return $this->bookmarkFilter->filter(
$bookmarks = $this->bookmarkFilter->filter(
BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
[$searchTags, $searchTerm],
$caseSensitive,
$visibility,
$untaggedOnly
);

return SearchResult::getSearchResult(
$bookmarks,
$pagination['offset'] ?? 0,
$pagination['limit'] ?? null,
$pagination['allowOutOfBounds'] ?? false
);
}

/**
Expand Down Expand Up @@ -282,7 +300,7 @@ public function exists(int $id, string $visibility = null): bool
*/
public function count(string $visibility = null): int
{
return count($this->search([], $visibility));
return $this->search([], $visibility)->getResultCount();
}

/**
Expand All @@ -305,10 +323,10 @@ public function save(): void
*/
public function bookmarksCountPerTag(array $filteringTags = [], string $visibility = null): array
{
$bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
$searchResult = $this->search(['searchtags' => $filteringTags], $visibility);
$tags = [];
$caseMapping = [];
foreach ($bookmarks as $bookmark) {
foreach ($searchResult->getBookmarks() as $bookmark) {
foreach ($bookmark->getTags() as $tag) {
if (
empty($tag)
Expand Down Expand Up @@ -357,7 +375,7 @@ public function findByDate(
$previous = null;
$next = null;

foreach ($this->search([], null, false, false, true) as $bookmark) {
foreach ($this->search([], null, false, false, true)->getBookmarks() as $bookmark) {
if ($to < $bookmark->getCreated()) {
$next = $bookmark->getCreated();
} elseif ($from < $bookmark->getCreated() && $to > $bookmark->getCreated()) {
Expand All @@ -378,7 +396,7 @@ public function findByDate(
*/
public function getLatest(): ?Bookmark
{
foreach ($this->search([], null, false, false, true) as $bookmark) {
foreach ($this->search([], null, false, false, true)->getBookmarks() as $bookmark) {
return $bookmark;
}

Expand Down
8 changes: 5 additions & 3 deletions application/bookmark/BookmarkServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,18 @@ public function findByUrl(string $url): ?Bookmark;
* @param bool $caseSensitive
* @param bool $untaggedOnly
* @param bool $ignoreSticky
* @param array $pagination This array can contain the following keys for pagination: limit, offset.
*
* @return Bookmark[]
* @return SearchResult
*/
public function search(
array $request = [],
string $visibility = null,
bool $caseSensitive = false,
bool $untaggedOnly = false,
bool $ignoreSticky = false
);
bool $ignoreSticky = false,
array $pagination = []
): SearchResult;

/**
* Get a single bookmark by its ID.
Expand Down
128 changes: 128 additions & 0 deletions application/bookmark/SearchResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

namespace Shaarli\Bookmark;

/**
* Read-only class used to represent search result, including pagination.
*/
class SearchResult
{
/** @var Bookmark[] List of result bookmarks with pagination applied */
protected $bookmarks;

/** @var int number of Bookmarks found, with pagination applied */
protected $resultCount;

/** @var int total number of result found */
protected $totalCount;

/** @var int pagination: limit number of result bookmarks */
protected $limit;

/** @var int pagination: offset to apply to complete result list */
protected $offset;

public function __construct(array $bookmarks, int $totalCount, int $offset, ?int $limit)
{
$this->bookmarks = $bookmarks;
$this->resultCount = count($bookmarks);
$this->totalCount = $totalCount;
$this->limit = $limit;
$this->offset = $offset;
}

/**
* Build a SearchResult from provided full result set and pagination settings.
*
* @param array $bookmarks Full set of result which will be filtered
* @param int $offset Start recording results from $offset
* @param int|null $limit End recording results after $limit bookmarks is reached
* @param bool $allowOutOfBounds Set to false to display the last page if the offset is out of bound,
* return empty result set otherwise (default: false)
*
* @return SearchResult
*/
public static function getSearchResult(
array $bookmarks,
int $offset = 0,
?int $limit = null,
bool $allowOutOfBounds = false
): self {
$totalCount = count($bookmarks);
if (!$allowOutOfBounds && $offset > $totalCount) {
$offset = $limit === null ? 0 : $limit * -1;
}

return new static(
array_slice($bookmarks, $offset, $limit, true),
$totalCount,
$offset,
$limit
);
}

/** @return Bookmark[] List of result bookmarks with pagination applied */
public function getBookmarks(): array
{
return $this->bookmarks;
}

/** @return int number of Bookmarks found, with pagination applied */
public function getResultCount(): int
{
return $this->resultCount;
}

/** @return int total number of result found */
public function getTotalCount(): int
{
return $this->totalCount;
}

/** @return int pagination: limit number of result bookmarks */
public function getLimit(): ?int
{
return $this->limit;
}

/** @return int pagination: offset to apply to complete result list */
public function getOffset(): int
{
return $this->offset;
}

/** @return int Current page of result set in complete results */
public function getPage(): int
{
if (empty($this->limit)) {
return $this->offset === 0 ? 1 : 2;
}
$base = $this->offset >= 0 ? $this->offset : $this->totalCount + $this->offset;

return (int) ceil($base / $this->limit) + 1;
}

/** @return int Get the # of the last page */
public function getLastPage(): int
{
if (empty($this->limit)) {
return $this->offset === 0 ? 1 : 2;
}

return (int) ceil($this->totalCount / $this->limit);
}

/** @return bool Either the current page is the last one or not */
public function isLastPage(): bool
{
return $this->getPage() === $this->getLastPage();
}

/** @return bool Either the current page is the first one or not */
public function isFirstPage(): bool
{
return $this->offset === 0;
}
}
Loading

0 comments on commit 5bc3b6a

Please sign in to comment.