Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ListItems
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 10
420
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setOptions
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getFromRecord
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFromSearch
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildListChannels
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 getListsById
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addPublicLists
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getLists
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 getListsByTagAndId
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getChannelFromList
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * "List items" channel provider.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2016.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Channels
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development Wiki
28 */
29
30namespace VuFind\ChannelProvider;
31
32use Laminas\Mvc\Controller\Plugin\Url;
33use Laminas\Stdlib\Parameters;
34use VuFind\Db\Entity\UserListEntityInterface;
35use VuFind\Db\Service\UserListServiceInterface;
36use VuFind\RecordDriver\AbstractBase as RecordDriver;
37use VuFind\Search\Base\Results;
38use VuFind\Tags\TagsService;
39
40use function count;
41
42/**
43 * "List items" channel provider.
44 *
45 * @category VuFind
46 * @package  Channels
47 * @author   Demian Katz <demian.katz@villanova.edu>
48 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
49 * @link     https://vufind.org/wiki/development Wiki
50 */
51class ListItems extends AbstractChannelProvider
52{
53    use \VuFind\I18n\Translator\TranslatorAwareTrait;
54
55    /**
56     * IDs of lists to display
57     *
58     * @var array
59     */
60    protected $ids;
61
62    /**
63     * Tags of lists to display
64     *
65     * @var array
66     */
67    protected $tags;
68
69    /**
70     * Whether to use AND operator when filtering by tag.
71     *
72     * @var bool
73     */
74    protected $andTags;
75
76    /**
77     * Should we pull in public list results in addition to the inclusion list in
78     * $ids?
79     *
80     * @var bool
81     */
82    protected $displayPublicLists;
83
84    /**
85     * How many lists should we display before switching over to tokens?
86     *
87     * @var int
88     */
89    protected $initialListsToDisplay;
90
91    /**
92     * Constructor
93     *
94     * @param UserListServiceInterface             $userListService UserList database service
95     * @param Url                                  $url             URL helper
96     * @param \VuFind\Search\Results\PluginManager $resultsManager  Results manager
97     * @param TagsService                          $tagsService     Tags service
98     * @param array                                $options         Settings (optional)
99     */
100    public function __construct(
101        protected UserListServiceInterface $userListService,
102        protected Url $url,
103        protected \VuFind\Search\Results\PluginManager $resultsManager,
104        protected TagsService $tagsService,
105        array $options = []
106    ) {
107        $this->setOptions($options);
108    }
109
110    /**
111     * Set the options for the provider.
112     *
113     * @param array $options Options
114     *
115     * @return void
116     */
117    public function setOptions(array $options)
118    {
119        $this->ids = $options['ids'] ?? [];
120        $this->tags = $options['tags'] ?? [];
121        $this->andTags
122            = 'or' !== trim(strtolower($options['tagsOperator'] ?? 'AND'));
123
124        $this->displayPublicLists = isset($options['displayPublicLists'])
125            ? (bool)$options['displayPublicLists'] : true;
126        $this->initialListsToDisplay = $options['initialListsToDisplay'] ?? 2;
127    }
128
129    /**
130     * Return channel information derived from a record driver object.
131     *
132     * @param RecordDriver $driver       Record driver
133     * @param string       $channelToken Token identifying a single specific channel
134     * to load (if omitted, all channels will be loaded)
135     *
136     * @return array
137     *
138     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
139     */
140    public function getFromRecord(RecordDriver $driver, $channelToken = null)
141    {
142        return $this->buildListChannels($channelToken);
143    }
144
145    /**
146     * Return channel information derived from a search results object.
147     *
148     * @param Results $results      Search results
149     * @param string  $channelToken Token identifying a single specific channel
150     * to load (if omitted, all channels will be loaded)
151     *
152     * @return array
153     *
154     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
155     */
156    public function getFromSearch(Results $results, $channelToken = null)
157    {
158        return $this->buildListChannels($channelToken);
159    }
160
161    /**
162     * Build all of the channel data.
163     *
164     * @param string $channelToken Token identifying a single specific channel
165     * to load (if omitted, all channels will be loaded)
166     *
167     * @return array
168     */
169    protected function buildListChannels($channelToken)
170    {
171        $channels = [];
172        $lists = $channelToken
173            ? $this->getListsById([$channelToken]) : $this->getLists();
174        foreach ($lists as $list) {
175            $tokenOnly = (count($channels) >= $this->initialListsToDisplay);
176            $channel = $this->getChannelFromList($list, $tokenOnly);
177            if ($tokenOnly || count($channel['contents']) > 0) {
178                $channels[] = $channel;
179            }
180        }
181        return $channels;
182    }
183
184    /**
185     * Get a list of lists, identified by ID; filter to public lists only.
186     *
187     * @param int[] $ids IDs to retrieve
188     *
189     * @return UserListEntityInterface[]
190     */
191    protected function getListsById(array $ids): array
192    {
193        return $this->userListService->getPublicLists($ids);
194    }
195
196    /**
197     * Given an array of lists, add public lists if configured to do so.
198     *
199     * @param UserListEntityInterface[] $lists List to expand.
200     *
201     * @return UserListEntityInterface[]
202     */
203    protected function addPublicLists(array $lists): array
204    {
205        return $this->displayPublicLists
206            ? array_merge($lists, $this->userListService->getPublicLists([], $lists))
207            : $lists;
208    }
209
210    /**
211     * Get a list of public lists to display:
212     *
213     * @return UserListEntityInterface[]
214     */
215    protected function getLists(): array
216    {
217        // Depending on whether tags are configured, we use different methods to
218        // fetch the base list of lists...
219        $baseLists = $this->tags
220            ? $this->getListsByTagAndId()
221            : $this->getListsById($this->ids);
222
223        // Sort lists by ID list, if necessary:
224        if (!empty($baseLists) && $this->ids) {
225            $orderIds = (array)$this->ids;
226            $sortFn = function (UserListEntityInterface $left, UserListEntityInterface $right) use ($orderIds) {
227                return
228                    array_search($left->getId(), $orderIds)
229                    <=> array_search($right->getId(), $orderIds);
230            };
231            usort($baseLists, $sortFn);
232        }
233
234        // Next, we add other public lists if necessary:
235        return $this->addPublicLists($baseLists);
236    }
237
238    /**
239     * Get a list of public lists, identified by ID and tag.
240     *
241     * @return UserListEntityInterface[]
242     */
243    protected function getListsByTagAndId(): array
244    {
245        // Get public lists by search criteria
246        return $this->tagsService->getUserListsByTagAndId(
247            $this->tags,
248            $this->ids,
249            true,
250            $this->andTags
251        );
252    }
253
254    /**
255     * Given a list object, return a channel array.
256     *
257     * @param UserListEntityInterface $list      User list
258     * @param bool                    $tokenOnly Return only token information?
259     *
260     * @return array
261     */
262    protected function getChannelFromList(UserListEntityInterface $list, bool $tokenOnly): array
263    {
264        $retVal = [
265            'title' => $list->getTitle(),
266            'providerId' => $this->providerId,
267            'token' => $list->getId(),
268            'links' => [],
269        ];
270        if ($tokenOnly) {
271            return $retVal;
272        }
273        $results = $this->resultsManager->get('Favorites');
274        $results->getParams()->initFromRequest(new Parameters(['id' => $list->getId()]));
275        $retVal['contents'] = $this->summarizeRecordDrivers($results->getResults());
276        $retVal['links'][] = [
277            'label' => 'channel_search',
278            'icon' => 'fa-list',
279            'url' => $this->url->fromRoute('userList', ['id' => $list->getId()]),
280        ];
281        return $retVal;
282    }
283}