Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
1.64% covered (danger)
1.64%
1 / 61
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserResource
1.64% covered (danger)
1.64%
1 / 61
14.29% covered (danger)
14.29%
1 / 7
259.62
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSavedData
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 createOrUpdateLink
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 destroyLinks
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 getStatistics
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
2
 getDuplicates
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 deduplicate
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3/**
4 * Table Definition for user_resource
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
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  Db_Table
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 Main Page
28 */
29
30namespace VuFind\Db\Table;
31
32use Laminas\Db\Adapter\Adapter;
33use Laminas\Db\Sql\Expression;
34use Laminas\Db\Sql\Select;
35use VuFind\Db\Row\RowGateway;
36use VuFind\Db\Service\DbServiceAwareInterface;
37use VuFind\Db\Service\DbServiceAwareTrait;
38use VuFind\Db\Service\ResourceTagsServiceInterface;
39use VuFind\Db\Service\UserResourceServiceInterface;
40
41/**
42 * Table Definition for user_resource
43 *
44 * @category VuFind
45 * @package  Db_Table
46 * @author   Demian Katz <demian.katz@villanova.edu>
47 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
48 * @link     https://vufind.org Main Page
49 */
50class UserResource extends Gateway implements DbServiceAwareInterface
51{
52    use DbServiceAwareTrait;
53
54    /**
55     * Constructor
56     *
57     * @param Adapter       $adapter Database adapter
58     * @param PluginManager $tm      Table manager
59     * @param array         $cfg     Laminas configuration
60     * @param RowGateway    $rowObj  Row prototype object (null for default)
61     * @param string        $table   Name of database table to interface with
62     */
63    public function __construct(
64        Adapter $adapter,
65        PluginManager $tm,
66        $cfg,
67        ?RowGateway $rowObj = null,
68        $table = 'user_resource'
69    ) {
70        parent::__construct($adapter, $tm, $cfg, $rowObj, $table);
71    }
72
73    /**
74     * Get information saved in a user's favorites for a particular record.
75     *
76     * @param string $resourceId ID of record being checked.
77     * @param string $source     Source of record to look up
78     * @param int    $listId     Optional list ID (to limit results to a particular
79     * list).
80     * @param int    $userId     Optional user ID (to limit results to a particular
81     * user).
82     *
83     * @return \Laminas\Db\ResultSet\AbstractResultSet
84     */
85    public function getSavedData(
86        $resourceId,
87        $source = DEFAULT_SEARCH_BACKEND,
88        $listId = null,
89        $userId = null
90    ) {
91        $callback = function ($select) use ($resourceId, $source, $listId, $userId) {
92            $select->columns(
93                [
94                    new Expression(
95                        'DISTINCT(?)',
96                        ['user_resource.id'],
97                        [Expression::TYPE_IDENTIFIER]
98                    ), Select::SQL_STAR,
99                ]
100            );
101            $select->join(
102                ['r' => 'resource'],
103                'r.id = user_resource.resource_id',
104                []
105            );
106            $select->join(
107                ['ul' => 'user_list'],
108                'user_resource.list_id = ul.id',
109                ['list_title' => 'title', 'list_id' => 'id']
110            );
111            $select->where->equalTo('r.source', $source)
112                ->equalTo('r.record_id', $resourceId);
113
114            if (null !== $userId) {
115                $select->where->equalTo('user_resource.user_id', $userId);
116            }
117            if (null !== $listId) {
118                $select->where->equalTo('user_resource.list_id', $listId);
119            }
120        };
121        return $this->select($callback);
122    }
123
124    /**
125     * Create link if one does not exist; update notes if one does.
126     *
127     * @param string $resource_id ID of resource to link up
128     * @param string $user_id     ID of user creating link
129     * @param string $list_id     ID of list to link up
130     * @param string $notes       Notes to associate with link
131     *
132     * @return \VuFind\Db\Row\UserResource
133     *
134     * @deprecated Use UserResourceServiceInterface::createOrUpdateLink()
135     */
136    public function createOrUpdateLink(
137        $resource_id,
138        $user_id,
139        $list_id,
140        $notes = ''
141    ) {
142        return $this->getDbService(UserResourceServiceInterface::class)
143            ->createOrUpdateLink($resource_id, $user_id, $list_id, $notes);
144    }
145
146    /**
147     * Unlink rows for the specified resource. This will also automatically remove
148     * any tags associated with the relationship.
149     *
150     * @param string|array $resource_id ID (or array of IDs) of resource(s) to
151     * unlink (null for ALL matching resources)
152     * @param string       $user_id     ID of user removing links
153     * @param string       $list_id     ID of list to unlink
154     * (null for ALL matching lists, with the destruction of all tags associated
155     * with the $resource_id value; true for ALL matching lists, but retaining
156     * any tags associated with the $resource_id independently of lists)
157     *
158     * @return void
159     *
160     * @deprecated
161     */
162    public function destroyLinks($resource_id, $user_id, $list_id = null)
163    {
164        // Remove any tags associated with the links we are removing; we don't
165        // want to leave orphaned tags in the resource_tags table after we have
166        // cleared out favorites in user_resource!
167        $resourceTagsService = $this->getDbService(ResourceTagsServiceInterface::class);
168        if ($list_id === true) {
169            $resourceTagsService->destroyAllListResourceTagsLinksForUser($resource_id, $user_id);
170        } else {
171            $resourceTagsService->destroyResourceTagsLinksForUser($resource_id, $user_id, $list_id);
172        }
173
174        // Now build the where clause to figure out which rows to remove:
175        $callback = function ($select) use ($resource_id, $user_id, $list_id) {
176            $select->where->equalTo('user_id', $user_id);
177            if (null !== $resource_id) {
178                $select->where->in('resource_id', (array)$resource_id);
179            }
180            // null or true values of $list_id have different meanings in the
181            // context of the destroyResourceTagsLinksForUser() call above, since
182            // some tags have a null $list_id value. In the case of user_resource
183            // rows, however, every row has a non-null $list_id value, so the
184            // two cases are equivalent and may be handled identically.
185            if (null !== $list_id && true !== $list_id) {
186                $select->where->equalTo('list_id', $list_id);
187            }
188        };
189
190        // Delete the rows:
191        $this->delete($callback);
192    }
193
194    /**
195     * Get statistics on use of lists.
196     *
197     * @return array
198     */
199    public function getStatistics()
200    {
201        $select = $this->sql->select();
202        $select->columns(
203            [
204                'users' => new Expression(
205                    'COUNT(DISTINCT(?))',
206                    ['user_id'],
207                    [Expression::TYPE_IDENTIFIER]
208                ),
209                'lists' => new Expression(
210                    'COUNT(DISTINCT(?))',
211                    ['list_id'],
212                    [Expression::TYPE_IDENTIFIER]
213                ),
214                'resources' => new Expression(
215                    'COUNT(DISTINCT(?))',
216                    ['resource_id'],
217                    [Expression::TYPE_IDENTIFIER]
218                ),
219                'total' => new Expression('COUNT(*)'),
220            ]
221        );
222        $statement = $this->sql->prepareStatementForSqlObject($select);
223        $result = $statement->execute();
224        return (array)$result->current();
225    }
226
227    /**
228     * Get a list of duplicate rows (this sometimes happens after merging IDs,
229     * for example after a Summon resource ID changes).
230     *
231     * @return mixed
232     */
233    public function getDuplicates()
234    {
235        $callback = function ($select) {
236            $select->columns(
237                [
238                    'resource_id' => new Expression(
239                        'MIN(?)',
240                        ['resource_id'],
241                        [Expression::TYPE_IDENTIFIER]
242                    ),
243                    'list_id' => new Expression(
244                        'MIN(?)',
245                        ['list_id'],
246                        [Expression::TYPE_IDENTIFIER]
247                    ),
248                    'user_id' => new Expression(
249                        'MIN(?)',
250                        ['user_id'],
251                        [Expression::TYPE_IDENTIFIER]
252                    ),
253                    'cnt' => new Expression(
254                        'COUNT(?)',
255                        ['resource_id'],
256                        [Expression::TYPE_IDENTIFIER]
257                    ),
258                    'id' => new Expression(
259                        'MIN(?)',
260                        ['id'],
261                        [Expression::TYPE_IDENTIFIER]
262                    ),
263                ]
264            );
265            $select->group(['resource_id', 'list_id', 'user_id']);
266            $select->having('COUNT(resource_id) > 1');
267        };
268        return $this->select($callback);
269    }
270
271    /**
272     * Deduplicate rows (sometimes necessary after merging foreign key IDs).
273     *
274     * @return void
275     */
276    public function deduplicate()
277    {
278        foreach ($this->getDuplicates() as $dupe) {
279            // Do this as a transaction to prevent odd behavior:
280            $connection = $this->getAdapter()->getDriver()->getConnection();
281            $connection->beginTransaction();
282
283            // Merge notes together...
284            $mainCriteria = [
285                'resource_id' => $dupe['resource_id'],
286                'list_id' => $dupe['list_id'],
287                'user_id' => $dupe['user_id'],
288            ];
289            $dupeRows = $this->select($mainCriteria);
290            $notes = [];
291            foreach ($dupeRows as $row) {
292                if (!empty($row['notes'])) {
293                    $notes[] = $row['notes'];
294                }
295            }
296            $this->update(
297                ['notes' => implode(' ', $notes)],
298                ['id' => $dupe['id']]
299            );
300            // Now delete extra rows...
301            $callback = function ($select) use ($dupe, $mainCriteria) {
302                // match on all relevant IDs in duplicate group
303                $select->where($mainCriteria);
304                // getDuplicates returns the minimum id in the set, so we want to
305                // delete all of the duplicates with a higher id value.
306                $select->where->greaterThan('id', $dupe['id']);
307            };
308            $this->delete($callback);
309
310            // Done -- commit the transaction:
311            $connection->commit();
312        }
313    }
314}