]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Handle pagination through BookmarkService 1697/head
authorArthurHoaro <arthur@hoa.ro>
Wed, 20 Jan 2021 13:45:59 +0000 (14:45 +0100)
committerArthurHoaro <arthur@hoa.ro>
Wed, 20 Jan 2021 14:01:29 +0000 (15:01 +0100)
Handle all search results through SearchResult object.
This is a required step toward implementing a BookmarkService based on SQL database.

Related to #953

20 files changed:
application/api/controllers/Links.php
application/api/controllers/Tags.php
application/bookmark/BookmarkFileService.php
application/bookmark/BookmarkServiceInterface.php
application/bookmark/SearchResult.php [new file with mode: 0644]
application/feed/FeedBuilder.php
application/front/controller/admin/ManageTagController.php
application/front/controller/admin/ThumbnailsController.php
application/front/controller/visitor/BookmarkListController.php
application/front/controller/visitor/DailyController.php
application/front/controller/visitor/PictureWallController.php
application/netscape/NetscapeBookmarkUtils.php
application/updater/Updater.php
tests/bookmark/BookmarkFileServiceTest.php
tests/bookmark/SearchResultTest.php [new file with mode: 0644]
tests/front/controller/admin/ManageTagControllerTest.php
tests/front/controller/admin/ThumbnailsControllerTest.php
tests/front/controller/visitor/BookmarkListControllerTest.php
tests/front/controller/visitor/DailyControllerTest.php
tests/front/controller/visitor/PictureWallControllerTest.php

index b83b2260f4ef670ad091c35d55620968eabd38ad..fe4bdc9f5a50f823872dca17b207354640a81325 100644 (file)
@@ -36,13 +36,6 @@ 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');
@@ -50,9 +43,6 @@ class Links extends ApiController
             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');
@@ -61,23 +51,33 @@ class Links extends ApiController
         } 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);
index e60e00a7058365d8adc8d850601ec41b5c5b4a08..5a23f6db7d314ff3e7cd3a376392575616426287 100644 (file)
@@ -122,12 +122,12 @@ class Tags extends ApiController
             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);
@@ -157,12 +157,12 @@ class Tags extends ApiController
             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);
index 6666a251c821a9eb83e710ddb48973e14008261d..8ea37427a02a6af364d6a1b79b0e5f921e2ebd0c 100644 (file)
@@ -55,8 +55,12 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
-    {
+    public function __construct(
+        ConfigManager $conf,
+        History $history,
+        Mutex $mutex,
+        bool $isLoggedIn
+    ) {
         $this->conf = $conf;
         $this->history = $history;
         $this->mutex = $mutex;
@@ -129,8 +133,9 @@ class BookmarkFileService implements BookmarkServiceInterface
         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;
         }
@@ -143,13 +148,20 @@ class BookmarkFileService implements BookmarkServiceInterface
             $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
+        );
     }
 
     /**
@@ -282,7 +294,7 @@ class BookmarkFileService implements BookmarkServiceInterface
      */
     public function count(string $visibility = null): int
     {
-        return count($this->search([], $visibility));
+        return $this->search([], $visibility)->getResultCount();
     }
 
     /**
@@ -305,10 +317,10 @@ class BookmarkFileService implements BookmarkServiceInterface
      */
     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)
@@ -357,7 +369,7 @@ class BookmarkFileService implements BookmarkServiceInterface
         $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()) {
@@ -378,7 +390,7 @@ class BookmarkFileService implements BookmarkServiceInterface
      */
     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;
         }
 
index 08cdbb4ed4055cc3f6ef2672b991f9c3b1cfeda7..4b1f0daa69470e4109462b7ad1954a17c038ca58 100644 (file)
@@ -44,16 +44,18 @@ interface BookmarkServiceInterface
      * @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.
diff --git a/application/bookmark/SearchResult.php b/application/bookmark/SearchResult.php
new file mode 100644 (file)
index 0000000..c0bce31
--- /dev/null
@@ -0,0 +1,136 @@
+<?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 Bookmark[] $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(
+        $bookmarks,
+        int $offset = 0,
+        ?int $limit = null,
+        bool $allowOutOfBounds = false
+    ): self {
+        $totalCount = count($bookmarks);
+        if (!$allowOutOfBounds && $offset > $totalCount) {
+            $offset = $limit === null ? 0 : $limit * -1;
+        }
+
+        if ($bookmarks instanceof BookmarkArray) {
+            $buffer = [];
+            foreach ($bookmarks as $key => $value) {
+                $buffer[$key] = $value;
+            }
+            $bookmarks = $buffer;
+        }
+
+        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;
+    }
+}
index ed62af26e1c4b26ae4eb481e37ab32f0a5d9988a..d5d74fd143adb105ede408865b7914681bad6ece 100644 (file)
@@ -102,22 +102,16 @@ class FeedBuilder
             $userInput['searchtags'] = false;
         }
 
-        // Optionally filter the results:
-        $linksToDisplay = $this->linkDB->search($userInput ?? [], null, false, false, true);
-
-        $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay), $userInput);
+        $limit = $this->getLimit($userInput);
 
-        // Can't use array_keys() because $link is a LinkDB instance and not a real array.
-        $keys = [];
-        foreach ($linksToDisplay as $key => $value) {
-            $keys[] = $key;
-        }
+        // Optionally filter the results:
+        $searchResult = $this->linkDB->search($userInput ?? [], null, false, false, true, ['limit' => $limit]);
 
         $pageaddr = escape(index_url($this->serverInfo));
         $this->formatter->addContextData('index_url', $pageaddr);
-        $linkDisplayed = [];
-        for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) {
-            $linkDisplayed[$keys[$i]] = $this->buildItem($feedType, $linksToDisplay[$keys[$i]], $pageaddr);
+        $links = [];
+        foreach ($searchResult->getBookmarks() as $key => $bookmark) {
+            $links[$key] = $this->buildItem($feedType, $bookmark, $pageaddr);
         }
 
         $data['language'] = $this->getTypeLanguage($feedType);
@@ -128,7 +122,7 @@ class FeedBuilder
         $data['self_link'] = $pageaddr . $requestUri;
         $data['index_url'] = $pageaddr;
         $data['usepermalinks'] = $this->usePermalinks === true;
-        $data['links'] = $linkDisplayed;
+        $data['links'] = $links;
 
         return $data;
     }
@@ -268,19 +262,18 @@ class FeedBuilder
      * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS.
      * If 'nb' is set to 'all', display all filtered bookmarks (max parameter).
      *
-     * @param int   $max       maximum number of bookmarks to display.
      * @param array $userInput $_GET.
      *
      * @return int number of bookmarks to display.
      */
-    protected function getNbLinks($max, ?array $userInput)
+    protected function getLimit(?array $userInput)
     {
         if (empty($userInput['nb'])) {
             return self::$DEFAULT_NB_LINKS;
         }
 
         if ($userInput['nb'] == 'all') {
-            return $max;
+            return null;
         }
 
         $intNb = intval($userInput['nb']);
index 8675a0c580bec714c4829948f24e04867f06acbe..1333cce72ef104a8c093aec164da71486b7e95f4 100644 (file)
@@ -57,9 +57,12 @@ class ManageTagController extends ShaarliAdminController
         }
 
         // TODO: move this to bookmark service
-        $count = 0;
-        $bookmarks = $this->container->bookmarkService->search(['searchtags' => $fromTag], BookmarkFilter::$ALL, true);
-        foreach ($bookmarks as $bookmark) {
+        $searchResult = $this->container->bookmarkService->search(
+            ['searchtags' => $fromTag],
+            BookmarkFilter::$ALL,
+            true
+        );
+        foreach ($searchResult->getBookmarks() as $bookmark) {
             if (false === $isDelete) {
                 $bookmark->renameTag($fromTag, $toTag);
             } else {
@@ -68,11 +71,11 @@ class ManageTagController extends ShaarliAdminController
 
             $this->container->bookmarkService->set($bookmark, false);
             $this->container->history->updateLink($bookmark);
-            $count++;
         }
 
         $this->container->bookmarkService->save();
 
+        $count = $searchResult->getResultCount();
         if (true === $isDelete) {
             $alert = sprintf(
                 t('The tag was removed from %d bookmark.', 'The tag was removed from %d bookmarks.', $count),
index 94d97d4bd3bcbd53b3c89bd06a0df14049fc5988..5dfea0964378a13aaba22516d755137d2e8b734f 100644 (file)
@@ -22,7 +22,7 @@ class ThumbnailsController extends ShaarliAdminController
     public function index(Request $request, Response $response): Response
     {
         $ids = [];
-        foreach ($this->container->bookmarkService->search() as $bookmark) {
+        foreach ($this->container->bookmarkService->search()->getBookmarks() as $bookmark) {
             // A note or not HTTP(S)
             if ($bookmark->isNote() || !startsWith(strtolower($bookmark->getUrl()), 'http')) {
                 continue;
index fe8231be1574b0f1d07d6f62f2d7df3faa85b71f..321ca813b5cf0baffaa85180322d0ff167235d24 100644 (file)
@@ -36,7 +36,6 @@ class BookmarkListController extends ShaarliVisitorController
 
         $searchTags = normalize_spaces($request->getParam('searchtags') ?? '');
         $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));
-        ;
 
         // Filter bookmarks according search parameters.
         $visibility = $this->container->sessionManager->getSessionParameter('visibility');
@@ -44,39 +43,26 @@ class BookmarkListController extends ShaarliVisitorController
             'searchtags' => $searchTags,
             'searchterm' => $searchTerm,
         ];
-        $linksToDisplay = $this->container->bookmarkService->search(
-            $search,
-            $visibility,
-            false,
-            !!$this->container->sessionManager->getSessionParameter('untaggedonly')
-        ) ?? [];
-
-        // ---- Handle paging.
-        $keys = [];
-        foreach ($linksToDisplay as $key => $value) {
-            $keys[] = $key;
-        }
-
-        $linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
 
         // Select articles according to paging.
-        $pageCount = (int) ceil(count($keys) / $linksPerPage) ?: 1;
-        $page = (int) $request->getParam('page') ?? 1;
+        $page = (int) ($request->getParam('page') ?? 1);
         $page = $page < 1 ? 1 : $page;
-        $page = $page > $pageCount ? $pageCount : $page;
+        $linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
 
-        // Start index.
-        $i = ($page - 1) * $linksPerPage;
-        $end = $i + $linksPerPage;
+        $searchResult = $this->container->bookmarkService->search(
+            $search,
+            $visibility,
+            false,
+            !!$this->container->sessionManager->getSessionParameter('untaggedonly'),
+            false,
+            ['offset' => $linksPerPage * ($page - 1), 'limit' => $linksPerPage]
+        ) ?? [];
 
-        $linkDisp = [];
         $save = false;
-        while ($i < $end && $i < count($keys)) {
-            $save = $this->updateThumbnail($linksToDisplay[$keys[$i]], false) || $save;
-            $link = $formatter->format($linksToDisplay[$keys[$i]]);
-
-            $linkDisp[$keys[$i]] = $link;
-            $i++;
+        $links = [];
+        foreach ($searchResult->getBookmarks() as $key => $bookmark) {
+            $save = $this->updateThumbnail($bookmark, false) || $save;
+            $links[$key] = $formatter->format($bookmark);
         }
 
         if ($save) {
@@ -86,15 +72,10 @@ class BookmarkListController extends ShaarliVisitorController
         // Compute paging navigation
         $searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags);
         $searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm);
+        $page = $searchResult->getPage();
 
-        $previous_page_url = '';
-        if ($i !== count($keys)) {
-            $previous_page_url = '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl;
-        }
-        $next_page_url = '';
-        if ($page > 1) {
-            $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl;
-        }
+        $previousPageUrl = !$searchResult->isLastPage() ? '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl : '';
+        $nextPageUrl = !$searchResult->isFirstPage() ? '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl : '';
 
         $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
         $searchTagsUrlEncoded = array_map('urlencode', tags_str2array($searchTags, $tagsSeparator));
@@ -104,16 +85,16 @@ class BookmarkListController extends ShaarliVisitorController
         $data = array_merge(
             $this->initializeTemplateVars(),
             [
-                'previous_page_url' => $previous_page_url,
-                'next_page_url' => $next_page_url,
+                'previous_page_url' => $previousPageUrl,
+                'next_page_url' => $nextPageUrl,
                 'page_current' => $page,
-                'page_max' => $pageCount,
-                'result_count' => count($linksToDisplay),
+                'page_max' => $searchResult->getLastPage(),
+                'result_count' => $searchResult->getTotalCount(),
                 'search_term' => escape($searchTerm),
                 'search_tags' => escape($searchTags),
                 'search_tags_url' => $searchTagsUrlEncoded,
                 'visibility' => $visibility,
-                'links' => $linkDisp,
+                'links' => $links,
             ]
         );
 
index 29492a5f3c1f3acd50d65547d9d3543dbf7e7a8a..3739ec168aad81de897b2888b80357c5b534f903 100644 (file)
@@ -100,7 +100,7 @@ class DailyController extends ShaarliVisitorController
         $days = [];
         $format = DailyPageHelper::getFormatByType($type);
         $length = DailyPageHelper::getRssLengthByType($type);
-        foreach ($this->container->bookmarkService->search() as $bookmark) {
+        foreach ($this->container->bookmarkService->search()->getBookmarks() as $bookmark) {
             $day = $bookmark->getCreated()->format($format);
 
             // Stop iterating after DAILY_RSS_NB_DAYS entries
index 23553ee63105d3f212332a8174ee9f927710c7e0..9c8f07d7a18e07990c1a488224563e1ec199d104 100644 (file)
@@ -30,19 +30,19 @@ class PictureWallController extends ShaarliVisitorController
         );
 
         // Optionally filter the results:
-        $links = $this->container->bookmarkService->search($request->getQueryParams());
-        $linksToDisplay = [];
+        $bookmarks = $this->container->bookmarkService->search($request->getQueryParams())->getBookmarks();
+        $links = [];
 
         // Get only bookmarks which have a thumbnail.
         // Note: we do not retrieve thumbnails here, the request is too heavy.
         $formatter = $this->container->formatterFactory->getFormatter('raw');
-        foreach ($links as $key => $link) {
-            if (!empty($link->getThumbnail())) {
-                $linksToDisplay[] = $formatter->format($link);
+        foreach ($bookmarks as $key => $bookmark) {
+            if (!empty($bookmark->getThumbnail())) {
+                $links[] = $formatter->format($bookmark);
             }
         }
 
-        $data = ['linksToDisplay' => $linksToDisplay];
+        $data = ['linksToDisplay' => $links];
         $this->executePageHooks('render_picwall', $data, TemplatePage::PICTURE_WALL);
 
         foreach ($data as $key => $value) {
index 2d97b4c85dbb89a3a1b20a582e0da35b97759a3c..20715bd0d7a6d0e35f1e5f84a2b4e533006f7feb 100644 (file)
@@ -64,7 +64,7 @@ class NetscapeBookmarkUtils
         }
 
         $bookmarkLinks = [];
-        foreach ($this->bookmarkService->search([], $selection) as $bookmark) {
+        foreach ($this->bookmarkService->search([], $selection)->getBookmarks() as $bookmark) {
             $link = $formatter->format($bookmark);
             $link['taglist'] = implode(',', $bookmark->getTags());
             if ($bookmark->isNote() && $prependNoteUrl) {
index 4f557d0f58fabca7429684eadbf3dfcc82bb1f7c..11b6c051498381ad1b7d0e4c698ee381c82a18ea 100644 (file)
@@ -152,7 +152,7 @@ class Updater
     {
         $updated = false;
 
-        foreach ($this->bookmarkService->search() as $bookmark) {
+        foreach ($this->bookmarkService->search()->getBookmarks() as $bookmark) {
             if (
                 $bookmark->isNote()
                 && startsWith($bookmark->getUrl(), '?')
index f619aff3f7865d7aebddcd4fb06622b52521b2e0..d1af3fb04f6fffa71813ab4161d02ea6a500d0eb 100644 (file)
@@ -807,7 +807,7 @@ class BookmarkFileServiceTest extends TestCase
         $request = ['searchtags' => $tags];
         $this->assertEquals(
             2,
-            count($this->privateLinkDB->search($request, null, true))
+            count($this->privateLinkDB->search($request, null, true)->getBookmarks())
         );
     }
 
@@ -820,7 +820,7 @@ class BookmarkFileServiceTest extends TestCase
         $request = ['searchtags' => $tags];
         $this->assertEquals(
             2,
-            count($this->privateLinkDB->search($request, null, true))
+            count($this->privateLinkDB->search($request, null, true)->getBookmarks())
         );
     }
 
@@ -834,12 +834,12 @@ class BookmarkFileServiceTest extends TestCase
         $request = ['searchtags' => $tags];
         $this->assertEquals(
             1,
-            count($this->privateLinkDB->search($request, 'all', true))
+            count($this->privateLinkDB->search($request, 'all', true)->getBookmarks())
         );
 
         $this->assertEquals(
             0,
-            count($this->publicLinkDB->search($request, 'public', true))
+            count($this->publicLinkDB->search($request, 'public', true)->getBookmarks())
         );
     }
 
diff --git a/tests/bookmark/SearchResultTest.php b/tests/bookmark/SearchResultTest.php
new file mode 100644 (file)
index 0000000..12854c1
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Bookmark;
+
+use Shaarli\TestCase;
+
+/**
+ * Test SearchResult class.
+ */
+class SearchResultTest extends TestCase
+{
+    /** Create a SearchResult without any pagination parameter. */
+    public function testResultNoParameters(): void
+    {
+        $searchResult = SearchResult::getSearchResult($data = ['a', 'b', 'c', 'd', 'e', 'f']);
+
+        static::assertSame($data, $searchResult->getBookmarks());
+        static::assertSame(6, $searchResult->getResultCount());
+        static::assertSame(6, $searchResult->getTotalCount());
+        static::assertSame(null, $searchResult->getLimit());
+        static::assertSame(0, $searchResult->getOffset());
+        static::assertSame(1, $searchResult->getPage());
+        static::assertSame(1, $searchResult->getLastPage());
+        static::assertTrue($searchResult->isFirstPage());
+        static::assertTrue($searchResult->isLastPage());
+    }
+
+    /** Create a SearchResult with only an offset parameter */
+    public function testResultWithOffset(): void
+    {
+        $searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 2);
+
+        static::assertSame([2 => 'c', 3 => 'd', 4 => 'e', 5 => 'f'], $searchResult->getBookmarks());
+        static::assertSame(4, $searchResult->getResultCount());
+        static::assertSame(6, $searchResult->getTotalCount());
+        static::assertSame(null, $searchResult->getLimit());
+        static::assertSame(2, $searchResult->getOffset());
+        static::assertSame(2, $searchResult->getPage());
+        static::assertSame(2, $searchResult->getLastPage());
+        static::assertFalse($searchResult->isFirstPage());
+        static::assertTrue($searchResult->isLastPage());
+    }
+
+    /** Create a SearchResult with only a limit parameter */
+    public function testResultWithLimit(): void
+    {
+        $searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 0, 2);
+
+        static::assertSame([0 => 'a', 1 => 'b'], $searchResult->getBookmarks());
+        static::assertSame(2, $searchResult->getResultCount());
+        static::assertSame(6, $searchResult->getTotalCount());
+        static::assertSame(2, $searchResult->getLimit());
+        static::assertSame(0, $searchResult->getOffset());
+        static::assertSame(1, $searchResult->getPage());
+        static::assertSame(3, $searchResult->getLastPage());
+        static::assertTrue($searchResult->isFirstPage());
+        static::assertFalse($searchResult->isLastPage());
+    }
+
+    /** Create a SearchResult with offset and limit parameters */
+    public function testResultWithLimitAndOffset(): void
+    {
+        $searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 2, 2);
+
+        static::assertSame([2 => 'c', 3 => 'd'], $searchResult->getBookmarks());
+        static::assertSame(2, $searchResult->getResultCount());
+        static::assertSame(6, $searchResult->getTotalCount());
+        static::assertSame(2, $searchResult->getLimit());
+        static::assertSame(2, $searchResult->getOffset());
+        static::assertSame(2, $searchResult->getPage());
+        static::assertSame(3, $searchResult->getLastPage());
+        static::assertFalse($searchResult->isFirstPage());
+        static::assertFalse($searchResult->isLastPage());
+    }
+
+    /** Create a SearchResult with offset and limit parameters displaying the last page */
+    public function testResultWithLimitAndOffsetLastPage(): void
+    {
+        $searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 4, 2);
+
+        static::assertSame([4 => 'e', 5 => 'f'], $searchResult->getBookmarks());
+        static::assertSame(2, $searchResult->getResultCount());
+        static::assertSame(6, $searchResult->getTotalCount());
+        static::assertSame(2, $searchResult->getLimit());
+        static::assertSame(4, $searchResult->getOffset());
+        static::assertSame(3, $searchResult->getPage());
+        static::assertSame(3, $searchResult->getLastPage());
+        static::assertFalse($searchResult->isFirstPage());
+        static::assertTrue($searchResult->isLastPage());
+    }
+
+    /** Create a SearchResult with offset and limit parameters out of bound (display the last page) */
+    public function testResultWithLimitAndOffsetOutOfBounds(): void
+    {
+        $searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 12, 2);
+
+        static::assertSame([4 => 'e', 5 => 'f'], $searchResult->getBookmarks());
+        static::assertSame(2, $searchResult->getResultCount());
+        static::assertSame(6, $searchResult->getTotalCount());
+        static::assertSame(2, $searchResult->getLimit());
+        static::assertSame(-2, $searchResult->getOffset());
+        static::assertSame(3, $searchResult->getPage());
+        static::assertSame(3, $searchResult->getLastPage());
+        static::assertFalse($searchResult->isFirstPage());
+        static::assertTrue($searchResult->isLastPage());
+    }
+
+    /** Create a SearchResult with offset and limit parameters out of bound (no result) */
+    public function testResultWithLimitAndOffsetOutOfBoundsNoResult(): void
+    {
+        $searchResult = SearchResult::getSearchResult(['a', 'b', 'c', 'd', 'e', 'f'], 12, 2, true);
+
+        static::assertSame([], $searchResult->getBookmarks());
+        static::assertSame(0, $searchResult->getResultCount());
+        static::assertSame(6, $searchResult->getTotalCount());
+        static::assertSame(2, $searchResult->getLimit());
+        static::assertSame(12, $searchResult->getOffset());
+        static::assertSame(7, $searchResult->getPage());
+        static::assertSame(3, $searchResult->getLastPage());
+        static::assertFalse($searchResult->isFirstPage());
+        static::assertFalse($searchResult->isLastPage());
+    }
+}
index af6f273f899db98758c420efcd2d9a9922e83373..56a64cbb79e7b3a2e3d8cd7129f6699fd83d35aa 100644 (file)
@@ -6,6 +6,7 @@ namespace Shaarli\Front\Controller\Admin;
 
 use Shaarli\Bookmark\Bookmark;
 use Shaarli\Bookmark\BookmarkFilter;
+use Shaarli\Bookmark\SearchResult;
 use Shaarli\Config\ConfigManager;
 use Shaarli\Front\Exception\WrongTokenException;
 use Shaarli\Security\SessionManager;
@@ -100,11 +101,11 @@ class ManageTagControllerTest extends TestCase
             ->expects(static::once())
             ->method('search')
             ->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true)
-            ->willReturnCallback(function () use ($bookmark1, $bookmark2): array {
+            ->willReturnCallback(function () use ($bookmark1, $bookmark2): SearchResult {
                 $bookmark1->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag');
                 $bookmark2->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag');
 
-                return [$bookmark1, $bookmark2];
+                return SearchResult::getSearchResult([$bookmark1, $bookmark2]);
             })
         ;
         $this->container->bookmarkService
@@ -153,11 +154,11 @@ class ManageTagControllerTest extends TestCase
             ->expects(static::once())
             ->method('search')
             ->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true)
-            ->willReturnCallback(function () use ($bookmark1, $bookmark2): array {
+            ->willReturnCallback(function () use ($bookmark1, $bookmark2): SearchResult {
                 $bookmark1->expects(static::once())->method('deleteTag')->with('old-tag');
                 $bookmark2->expects(static::once())->method('deleteTag')->with('old-tag');
 
-                return [$bookmark1, $bookmark2];
+                return SearchResult::getSearchResult([$bookmark1, $bookmark2]);
             })
         ;
         $this->container->bookmarkService
index e5749654bec896f8866e4daee2b79a692cbe2d80..0c9b63c3a44e8e0f695c16fcf688b9f59aee1c9a 100644 (file)
@@ -6,6 +6,7 @@ namespace Shaarli\Front\Controller\Admin;
 
 use Shaarli\Bookmark\Bookmark;
 use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
+use Shaarli\Bookmark\SearchResult;
 use Shaarli\TestCase;
 use Shaarli\Thumbnailer;
 use Slim\Http\Request;
@@ -40,12 +41,12 @@ class ThumbnailsControllerTest extends TestCase
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('search')
-            ->willReturn([
+            ->willReturn(SearchResult::getSearchResult([
                 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
                 (new Bookmark())->setId(2)->setUrl('?abcdef')->setTitle('Note 1'),
                 (new Bookmark())->setId(3)->setUrl('http://url2.tld')->setTitle('Title 2'),
                 (new Bookmark())->setId(4)->setUrl('ftp://domain.tld', ['ftp'])->setTitle('FTP'),
-            ])
+            ]))
         ;
 
         $result = $this->controller->index($request, $response);
index dec938f209516d65ff479021c2d6177c295a3d71..0fbab9d428dad727b792b6252ac1981ef1ccb5d3 100644 (file)
@@ -6,6 +6,7 @@ namespace Shaarli\Front\Controller\Visitor;
 
 use Shaarli\Bookmark\Bookmark;
 use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
+use Shaarli\Bookmark\SearchResult;
 use Shaarli\Config\ConfigManager;
 use Shaarli\Security\LoginManager;
 use Shaarli\TestCase;
@@ -45,13 +46,15 @@ class BookmarkListControllerTest extends TestCase
                 ['searchtags' => '', 'searchterm' => ''],
                 null,
                 false,
-                false
+                false,
+                false,
+                ['offset' => 0, 'limit' => 2]
             )
-            ->willReturn([
+            ->willReturn(SearchResult::getSearchResult([
                 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
                 (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
                 (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
-            ]
+            ], 0, 2)
         );
 
         $this->container->sessionManager
@@ -119,13 +122,15 @@ class BookmarkListControllerTest extends TestCase
                 ['searchtags' => '', 'searchterm' => ''],
                 null,
                 false,
-                false
+                false,
+                false,
+                ['offset' => 2, 'limit' => 2]
             )
-            ->willReturn([
+            ->willReturn(SearchResult::getSearchResult([
                 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
                 (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
                 (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
-            ])
+            ], 2, 2))
         ;
 
         $this->container->sessionManager
@@ -207,13 +212,15 @@ class BookmarkListControllerTest extends TestCase
                 ['searchtags' => 'abc@def', 'searchterm' => 'ghi jkl'],
                 'private',
                 false,
-                true
+                true,
+                false,
+                ['offset' => 0, 'limit' => 2]
             )
-            ->willReturn([
+            ->willReturn(SearchResult::getSearchResult([
                 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
                 (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
                 (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
-            ])
+            ], 0, 2))
         ;
 
         $result = $this->controller->index($request, $response);
@@ -358,13 +365,13 @@ class BookmarkListControllerTest extends TestCase
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('search')
-            ->willReturn([
+            ->willReturn(SearchResult::getSearchResult([
                 (new Bookmark())->setId(1)->setUrl('https://url1.tld')->setTitle('Title 1')->setThumbnail(false),
                 $b1 = (new Bookmark())->setId(2)->setUrl('https://url2.tld')->setTitle('Title 2'),
                 (new Bookmark())->setId(3)->setUrl('https://url3.tld')->setTitle('Title 3')->setThumbnail(false),
                 $b2 = (new Bookmark())->setId(2)->setUrl('https://url4.tld')->setTitle('Title 4'),
                 (new Bookmark())->setId(2)->setUrl('ftp://url5.tld', ['ftp'])->setTitle('Title 5'),
-            ])
+            ]))
         ;
         $this->container->bookmarkService
             ->expects(static::exactly(2))
index 70fbce5482d75ff9beef4c423dfb3d3e52de094e..821ba321ce265d69515a3d47b11ac34f5b30dd98 100644 (file)
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Shaarli\Front\Controller\Visitor;
 
 use Shaarli\Bookmark\Bookmark;
+use Shaarli\Bookmark\SearchResult;
 use Shaarli\Feed\CachedPage;
 use Shaarli\TestCase;
 use Slim\Http\Request;
@@ -347,13 +348,15 @@ class DailyControllerTest extends TestCase
         $request = $this->createMock(Request::class);
         $response = new Response();
 
-        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
-            (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
-            (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
-            (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
-            (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'),
-            (new Bookmark())->setId(5)->setCreated($dates[3])->setUrl('http://domain.tld/5'),
-        ]);
+        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn(
+            SearchResult::getSearchResult([
+                (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
+                (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
+                (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
+                (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'),
+                (new Bookmark())->setId(5)->setCreated($dates[3])->setUrl('http://domain.tld/5'),
+            ])
+        );
 
         $this->container->pageCacheManager
             ->expects(static::once())
@@ -454,7 +457,9 @@ class DailyControllerTest extends TestCase
         $request = $this->createMock(Request::class);
         $response = new Response();
 
-        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([]);
+        $this->container->bookmarkService
+            ->expects(static::once())->method('search')
+            ->willReturn(SearchResult::getSearchResult([]));
 
         // Save RainTPL assigned variables
         $assignedVariables = [];
@@ -613,11 +618,13 @@ class DailyControllerTest extends TestCase
         });
         $response = new Response();
 
-        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
-            (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
-            (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
-            (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
-        ]);
+        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn(
+            SearchResult::getSearchResult([
+                (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
+                (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
+                (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
+            ])
+        );
 
         // Save RainTPL assigned variables
         $assignedVariables = [];
@@ -674,11 +681,13 @@ class DailyControllerTest extends TestCase
         });
         $response = new Response();
 
-        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
-            (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
-            (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
-            (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
-        ]);
+        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn(
+            SearchResult::getSearchResult([
+                (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
+                (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
+                (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
+            ])
+        );
 
         // Save RainTPL assigned variables
         $assignedVariables = [];
index b868231dc92aaefebdd19bcebaff9c07320b6cc7..429e99a2dc0a9e56c1fe85c67d2131842e1a9d51 100644 (file)
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Shaarli\Front\Controller\Visitor;
 
 use Shaarli\Bookmark\Bookmark;
+use Shaarli\Bookmark\SearchResult;
 use Shaarli\Config\ConfigManager;
 use Shaarli\Front\Exception\ThumbnailsDisabledException;
 use Shaarli\TestCase;
@@ -50,17 +51,17 @@ class PictureWallControllerTest extends TestCase
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('search')
-            ->willReturnCallback(function (array $parameters, ?string $visibility): array {
+            ->willReturnCallback(function (array $parameters, ?string $visibility): SearchResult {
                 // Visibility is set through the container, not the call
                 static::assertNull($visibility);
 
                 // No query parameters
                 if (count($parameters) === 0) {
-                    return [
+                    return SearchResult::getSearchResult([
                         (new Bookmark())->setId(1)->setUrl('http://url.tld')->setThumbnail('thumb1'),
                         (new Bookmark())->setId(2)->setUrl('http://url2.tld'),
                         (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setThumbnail('thumb2'),
-                    ];
+                    ]);
                 }
             })
         ;