Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
3.77% covered (danger)
3.77%
2 / 53
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Resource
3.77% covered (danger)
3.77%
2 / 53
14.29% covered (danger)
14.29%
1 / 7
628.32
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 findResource
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 findResources
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFavorites
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
56
 findMissingMetadata
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateRecordId
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
 applySort
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3/**
4 * Table Definition for 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 Site
28 */
29
30namespace VuFind\Db\Table;
31
32use Laminas\Db\Adapter\Adapter;
33use Laminas\Db\Sql\Expression;
34use Laminas\Db\Sql\Select;
35use VuFind\Date\Converter as DateConverter;
36use VuFind\Db\Row\RowGateway;
37use VuFind\Db\Service\DbServiceAwareInterface;
38use VuFind\Db\Service\DbServiceAwareTrait;
39use VuFind\Db\Service\ResourceServiceInterface;
40
41use function in_array;
42
43/**
44 * Table Definition for resource
45 *
46 * @category VuFind
47 * @package  Db_Table
48 * @author   Demian Katz <demian.katz@villanova.edu>
49 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
50 * @link     https://vufind.org Main Site
51 */
52class Resource extends Gateway implements DbServiceAwareInterface
53{
54    use DbServiceAwareTrait;
55
56    /**
57     * Loader for record populator
58     *
59     * @var callable
60     */
61    protected $resourcePopulatorLoader;
62
63    /**
64     * Constructor
65     *
66     * @param Adapter       $adapter                 Database adapter
67     * @param PluginManager $tm                      Table manager
68     * @param array         $cfg                     Laminas configuration
69     * @param ?RowGateway   $rowObj                  Row prototype object (null for default)
70     * @param DateConverter $dateConverter           Date converter
71     * @param callable      $resourcePopulatorLoader Resource populator loader
72     * @param string        $table                   Name of database table to interface with
73     */
74    public function __construct(
75        Adapter $adapter,
76        PluginManager $tm,
77        array $cfg,
78        ?RowGateway $rowObj,
79        protected DateConverter $dateConverter,
80        callable $resourcePopulatorLoader,
81        string $table = 'resource'
82    ) {
83        $this->resourcePopulatorLoader = $resourcePopulatorLoader;
84        parent::__construct($adapter, $tm, $cfg, $rowObj, $table);
85    }
86
87    /**
88     * Look up a row for the specified resource.
89     *
90     * @param string                            $id     Record ID to look up
91     * @param string                            $source Source of record to look up
92     * @param bool                              $create If true, create the row if it
93     * does not yet exist.
94     * @param \VuFind\RecordDriver\AbstractBase $driver A record driver for the
95     * resource being created (optional -- improves efficiency if provided, but will
96     * be auto-loaded as needed if left null).
97     *
98     * @return \VuFind\Db\Row\Resource|null Matching row if found or created, null
99     * otherwise.
100     *
101     * @deprecated Use ResourceServiceInterface::getResourceByRecordId() or
102     * \VuFind\Record\ResourcePopulator::getOrCreateResourceForDriver() or
103     * \VuFind\Record\ResourcePopulator::getOrCreateResourceForRecordId() as appropriate.
104     */
105    public function findResource(
106        $id,
107        $source = DEFAULT_SEARCH_BACKEND,
108        $create = true,
109        $driver = null
110    ) {
111        if (empty($id)) {
112            throw new \Exception('Resource ID cannot be empty');
113        }
114        $select = $this->select(['record_id' => $id, 'source' => $source]);
115        $result = $select->current();
116
117        // Create row if it does not already exist and creation is enabled:
118        if (empty($result) && $create) {
119            $resourcePopulator = ($this->resourcePopulatorLoader)();
120            $result = $driver
121                ? $resourcePopulator->createAndPersistResourceForDriver($driver)
122                : $resourcePopulator->createAndPersistResourceForRecordId($id, $source);
123        }
124        return $result;
125    }
126
127    /**
128     * Look up a rowset for a set of specified resources.
129     *
130     * @param array  $ids    Array of IDs
131     * @param string $source Source of records to look up
132     *
133     * @return ResourceEntityInterface[]
134     *
135     * @deprecated Use \VuFind\Db\Service\ResourceServiceInterface::getResourcesByRecordIds()
136     */
137    public function findResources($ids, $source = DEFAULT_SEARCH_BACKEND)
138    {
139        return $this->getDbService(ResourceServiceInterface::class)->getResourcesByRecordIds($ids, $source);
140    }
141
142    /**
143     * Get a set of records from the requested favorite list.
144     *
145     * @param string $user              ID of user owning favorite list
146     * @param string $list              ID of list to retrieve (null for all favorites)
147     * @param array  $tags              Tags to use for limiting results
148     * @param string $sort              Resource table field to use for sorting (null for no particular sort).
149     * @param int    $offset            Offset for results
150     * @param int    $limit             Limit for results (null for none)
151     * @param ?bool  $caseSensitiveTags Should tags be searched case sensitively (null for configured default)
152     *
153     * @return \Laminas\Db\ResultSet\AbstractResultSet
154     */
155    public function getFavorites(
156        $user,
157        $list = null,
158        $tags = [],
159        $sort = null,
160        $offset = 0,
161        $limit = null,
162        $caseSensitiveTags = null
163    ) {
164        // Set up base query:
165        return $this->select(
166            function ($s) use ($user, $list, $tags, $sort, $offset, $limit, $caseSensitiveTags) {
167                $subQuery = $this->getDbTable('UserResource')
168                    ->getSql()
169                    ->select()
170                    ->quantifier(Select::QUANTIFIER_DISTINCT)
171                    ->columns(['resource_id']);
172                $subQuery->where->equalTo('user_id', $user);
173
174                // Adjust for list if necessary:
175                if (null !== $list) {
176                    $subQuery->where->equalTo('list_id', $list);
177                }
178                // Adjust for tags if necessary:
179                if (!empty($tags)) {
180                    $linkingTable = $this->getDbTable('ResourceTags');
181                    foreach ($tags as $tag) {
182                        $matches = $linkingTable->getResourcesForTag($tag, $user, $list, $caseSensitiveTags)->toArray();
183                        $getId = function ($i) {
184                            return $i['resource_id'];
185                        };
186                        $subQuery->where->in('resource_id', array_map($getId, $matches));
187                    }
188                }
189
190                $columns = [Select::SQL_STAR];
191                $s->columns($columns);
192                $s->where->in('id', $subQuery);
193                if ($offset > 0) {
194                    $s->offset($offset);
195                }
196                if (null !== $limit) {
197                    $s->limit($limit);
198                }
199
200                // Apply sorting, if necessary:
201                if (!empty($sort)) {
202                    Resource::applySort($s, $sort, 'resource', $columns);
203                }
204            }
205        );
206    }
207
208    /**
209     * Get a set of records that do not have metadata stored in the resource
210     * table.
211     *
212     * @return ResourceEntityInterface[]
213     *
214     * @deprecated Use \VuFind\Db\Service\ResourceServiceInterface::findMissingMetadata()
215     */
216    public function findMissingMetadata()
217    {
218        return $this->getDbService(ResourceServiceInterface::class)->findMissingMetadata();
219    }
220
221    /**
222     * Update the database to reflect a changed record identifier.
223     *
224     * @param string $oldId  Original record ID
225     * @param string $newId  Revised record ID
226     * @param string $source Record source
227     *
228     * @return void
229     *
230     * @deprecated Use \VuFind\Record\RecordIdUpdater::updateRecordId()
231     */
232    public function updateRecordId($oldId, $newId, $source = DEFAULT_SEARCH_BACKEND)
233    {
234        $resourceService = $this->getDbService(ResourceServiceInterface::class);
235        if (
236            $oldId !== $newId
237            && $resource = $resourceService->getResourceByRecordId($oldId, $source)
238        ) {
239            $tableObjects = [];
240            // Do this as a transaction to prevent odd behavior:
241            $connection = $this->getAdapter()->getDriver()->getConnection();
242            $connection->beginTransaction();
243            // Does the new ID already exist?
244            if ($newResource = $resourceService->getResourceByRecordId($newId, $source)) {
245                // Special case: merge new ID and old ID:
246                foreach (['comments', 'userresource', 'resourcetags'] as $table) {
247                    $tableObjects[$table] = $this->getDbTable($table);
248                    $tableObjects[$table]->update(
249                        ['resource_id' => $newResource->id],
250                        ['resource_id' => $resource->id]
251                    );
252                }
253                $resource->delete();
254            } else {
255                // Default case: just update the record ID:
256                $resource->record_id = $newId;
257                $resource->save();
258            }
259            // Done -- commit the transaction:
260            $connection->commit();
261
262            // Deduplicate rows where necessary (this can be safely done outside
263            // of the transaction):
264            if (isset($tableObjects['resourcetags'])) {
265                $tableObjects['resourcetags']->deduplicate();
266            }
267            if (isset($tableObjects['userresource'])) {
268                $tableObjects['userresource']->deduplicate();
269            }
270        }
271    }
272
273    /**
274     * Apply a sort parameter to a query on the resource table.
275     *
276     * @param \Laminas\Db\Sql\Select $query   Query to modify
277     * @param string                 $sort    Field to use for sorting (may include
278     * 'desc' qualifier)
279     * @param string                 $alias   Alias to the resource table (defaults to
280     * 'resource')
281     * @param array                  $columns Existing list of columns to select
282     *
283     * @return void
284     */
285    public static function applySort($query, $sort, $alias = 'resource', $columns = [])
286    {
287        // Apply sorting, if necessary:
288        $legalSorts = [
289            'title', 'title desc', 'author', 'author desc', 'year', 'year desc',
290        ];
291        if (!empty($sort) && in_array(strtolower($sort), $legalSorts)) {
292            // Strip off 'desc' to obtain the raw field name -- we'll need it
293            // to sort null values to the bottom:
294            $parts = explode(' ', $sort);
295            $rawField = trim($parts[0]);
296
297            // Start building the list of sort fields:
298            $order = [];
299
300            // The title field can't be null, so don't bother with the extra
301            // isnull() sort in that case.
302            if (strtolower($rawField) != 'title') {
303                $expression = new Expression(
304                    'case when ? is null then 1 else 0 end',
305                    [$alias . '.' . $rawField],
306                    [Expression::TYPE_IDENTIFIER]
307                );
308                $query->columns(array_merge($columns, [$expression]));
309                $order[] = $expression;
310            }
311
312            // Apply the user-specified sort:
313            $order[] = $alias . '.' . $sort;
314
315            // Inject the sort preferences into the query object:
316            $query->order($order);
317        }
318    }
319}