Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
19.10% covered (danger)
19.10%
17 / 89
45.45% covered (danger)
45.45%
10 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractBase
19.10% covered (danger)
19.10%
17 / 89
45.45% covered (danger)
45.45%
10 / 22
574.16
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
 setRawData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRawData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBreadcrumb
n/a
0 / 0
n/a
0 / 0
0
 getUniqueID
n/a
0 / 0
n/a
0 / 0
0
 getComments
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getSortTitle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTags
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 addTags
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 deleteTags
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getRatingData
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getRatingBreakdown
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 addOrUpdateRating
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getListNotes
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 getContainingLists
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 supportsAjaxStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 supportsOpenUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 supportsCoinsOpenUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRatingAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setExtraDetail
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCitationFormats
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 getSupportedCitationFormats
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExtraDetail
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tryMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * Abstract base record model.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010-2024.
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  RecordDrivers
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\RecordDriver;
31
32use VuFind\Db\Service\CommentsServiceInterface;
33use VuFind\Db\Service\TagServiceInterface;
34use VuFind\Db\Service\UserListServiceInterface;
35use VuFind\XSLT\Import\VuFind as ArticleStripper;
36
37use function is_callable;
38
39/**
40 * Abstract base record model.
41 *
42 * This abstract class defines the basic methods for modeling a record in VuFind.
43 *
44 * @category VuFind
45 * @package  RecordDrivers
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 */
50abstract class AbstractBase implements
51    \VuFind\Db\Service\DbServiceAwareInterface,
52    \VuFind\Db\Table\DbTableAwareInterface,
53    \VuFind\I18n\Translator\TranslatorAwareInterface,
54    \VuFindSearch\Response\RecordInterface
55{
56    use \VuFind\Db\Service\DbServiceAwareTrait;
57    use \VuFind\Db\Table\DbTableAwareTrait;
58    use \VuFind\I18n\Translator\TranslatorAwareTrait;
59    use \VuFindSearch\Response\RecordTrait;
60
61    /**
62     * For storing extra data with record
63     *
64     * @var array
65     */
66    protected $extraDetails = [];
67
68    /**
69     * Main VuFind configuration
70     *
71     * @var \Laminas\Config\Config
72     */
73    protected $mainConfig;
74
75    /**
76     * Record-specific configuration
77     *
78     * @var \Laminas\Config\Config
79     */
80    protected $recordConfig;
81
82    /**
83     * Raw data
84     *
85     * @var array
86     */
87    protected $fields = [];
88
89    /**
90     * Cache for rating data
91     *
92     * @var array
93     */
94    protected $ratingCache = [];
95
96    /**
97     * Constructor
98     *
99     * @param \Laminas\Config\Config $mainConfig   VuFind main configuration (omit
100     * for built-in defaults)
101     * @param \Laminas\Config\Config $recordConfig Record-specific configuration file
102     * (omit to use $mainConfig as $recordConfig)
103     */
104    public function __construct($mainConfig = null, $recordConfig = null)
105    {
106        $this->mainConfig = $mainConfig;
107        $this->recordConfig = $recordConfig ?? $mainConfig;
108    }
109
110    /**
111     * Set raw data to initialize the object.
112     *
113     * @param mixed $data Raw data representing the record; Record Model
114     * objects are normally constructed by Record Driver objects using data
115     * passed in from a Search Results object. The exact nature of the data may
116     * vary depending on the data source -- the important thing is that the
117     * Record Driver + Search Results objects work together correctly.
118     *
119     * @return void
120     */
121    public function setRawData($data)
122    {
123        $this->fields = $data;
124    }
125
126    /**
127     * Retrieve raw data from object (primarily for use in staff view and
128     * autocomplete; avoid using whenever possible).
129     *
130     * @return mixed
131     */
132    public function getRawData()
133    {
134        return $this->fields;
135    }
136
137    /**
138     * Get text that can be displayed to represent this record in breadcrumbs.
139     *
140     * @return string Breadcrumb text to represent this record.
141     */
142    abstract public function getBreadcrumb();
143
144    /**
145     * Return the unique identifier of this record for retrieving additional
146     * information (like tags and user comments) from the external MySQL database.
147     *
148     * @return string Unique identifier.
149     */
150    abstract public function getUniqueID();
151
152    /**
153     * Get comments associated with this record.
154     *
155     * @return array
156     *
157     * @deprecated Use CommentsServiceInterface::getRecordComments()
158     */
159    public function getComments()
160    {
161        return $this->getDbService(CommentsServiceInterface::class)->getRecordComments(
162            $this->getUniqueId(),
163            $this->getSourceIdentifier()
164        );
165    }
166
167    /**
168     * Get a sortable title for the record (i.e. no leading articles).
169     *
170     * @return string
171     */
172    public function getSortTitle()
173    {
174        // Child classes should override this with smarter behavior, and the "strip
175        // articles" logic probably belongs in a more appropriate place, but for now
176        // in the absence of a better plan, we'll just use the XSLT Importer's strip
177        // articles functionality.
178        return ArticleStripper::stripArticles($this->getBreadcrumb());
179    }
180
181    /**
182     * Get tags associated with this record.
183     *
184     * @param int    $list_id ID of list to load tags from (null for all lists)
185     * @param int    $user_id ID of user to load tags from (null for all users)
186     * @param string $sort    Sort type ('count' or 'tag')
187     * @param int    $ownerId ID of user to check for ownership
188     *
189     * @return array
190     *
191     * @deprecated Use TagServiceInterface::getRecordTags() or TagServiceInterface::getRecordTagsFromFavorites()
192     * or TagServiceInterface::getRecordTagsNotInFavorites()
193     */
194    public function getTags(
195        $list_id = null,
196        $user_id = null,
197        $sort = 'count',
198        $ownerId = null
199    ) {
200        return $this->getDbTable('Tags')->getForResource(
201            $this->getUniqueId(),
202            $this->getSourceIdentifier(),
203            0,
204            $list_id,
205            $user_id,
206            $sort,
207            $ownerId
208        );
209    }
210
211    /**
212     * Add tags to the record.
213     *
214     * @param UserEntityInterface $user The user posting the tag
215     * @param array               $tags The user-provided tags
216     *
217     * @return void
218     *
219     * @deprecated Use \VuFind\Tags\TagsService::linkTagsToRecord()
220     */
221    public function addTags($user, $tags)
222    {
223        $resources = $this->getDbTable('Resource');
224        $resource = $resources->findResource(
225            $this->getUniqueId(),
226            $this->getSourceIdentifier()
227        );
228        foreach ($tags as $tag) {
229            $resource->addTag($tag, $user);
230        }
231    }
232
233    /**
234     * Remove tags from the record.
235     *
236     * @param UserEntityInterface $user The user posting the tag
237     * @param array               $tags The user-provided tags
238     *
239     * @return void
240     *
241     * @deprecated Use \VuFind\Tags\TagsService::unlinkTagsFromRecord()
242     */
243    public function deleteTags($user, $tags)
244    {
245        $resources = $this->getDbTable('Resource');
246        $resource = $resources->findResource(
247            $this->getUniqueId(),
248            $this->getSourceIdentifier()
249        );
250        foreach ($tags as $tag) {
251            $resource->deleteTag($tag, $user);
252        }
253    }
254
255    /**
256     * Get rating information for this record.
257     *
258     * Returns an array with the following keys:
259     *
260     * rating - average rating (0-100)
261     * count  - count of ratings
262     *
263     * @param ?int $userId User ID, or null for all users
264     *
265     * @return array
266     *
267     * @deprecated Use \VuFind\Ratings\RatingsService::getRatingData()
268     */
269    public function getRatingData(?int $userId = null)
270    {
271        // Cache data since comments list may ask for same information repeatedly:
272        $cacheKey = $userId ?? '-';
273        if (!isset($this->ratingCache[$cacheKey])) {
274            $ratingsService = $this->getDbService(
275                \VuFind\Db\Service\RatingsServiceInterface::class
276            );
277            $this->ratingCache[$cacheKey] = $ratingsService->getRecordRatings(
278                $this->getUniqueId(),
279                $this->getSourceIdentifier(),
280                $userId
281            );
282        }
283        return $this->ratingCache[$cacheKey];
284    }
285
286    /**
287     * Get rating breakdown for this record.
288     *
289     * Returns an array with the following keys:
290     *
291     * rating - average rating (0-100)
292     * count  - count of ratings
293     * groups - grouped counts
294     *
295     * @param array $groups Group definition (key => [min, max])
296     *
297     * @return array
298     *
299     * @deprecated Use \VuFind\Ratings\RatingsService::getRatingBreakdown()
300     */
301    public function getRatingBreakdown(array $groups)
302    {
303        return $this->getDbService(\VuFind\Db\Service\RatingsServiceInterface::class)
304            ->getCountsForRecord(
305                $this->getUniqueId(),
306                $this->getSourceIdentifier(),
307                $groups
308            );
309    }
310
311    /**
312     * Add or update user's rating for the record.
313     *
314     * @param int  $userId ID of the user posting the rating
315     * @param ?int $rating The user-provided rating, or null to clear any existing
316     * rating
317     *
318     * @return void
319     *
320     * @deprecated Use \VuFind\Ratings\RatingsService::saveRating()
321     */
322    public function addOrUpdateRating(int $userId, ?int $rating): void
323    {
324        // Clear rating cache:
325        $this->ratingCache = [];
326        $resources = $this->getDbTable('Resource');
327        $resource = $resources->findResource(
328            $this->getUniqueId(),
329            $this->getSourceIdentifier()
330        );
331        $this->getDbService(\VuFind\Db\Service\RatingsServiceInterface::class)
332            ->addOrUpdateRating($resource, $userId, $rating);
333    }
334
335    /**
336     * Get notes associated with this record in user lists.
337     *
338     * @param int $list_id ID of list to load tags from (null for all lists)
339     * @param int $user_id ID of user to load tags from (null for all users)
340     *
341     * @return array
342     *
343     * @deprecated Use \VuFind\View\Helper\Root\Record::getListNotes()
344     */
345    public function getListNotes($list_id = null, $user_id = null)
346    {
347        $db = $this->getDbTable('UserResource');
348        $data = $db->getSavedData(
349            $this->getUniqueId(),
350            $this->getSourceIdentifier(),
351            $list_id,
352            $user_id
353        );
354        $notes = [];
355        foreach ($data as $current) {
356            if (!empty($current->notes)) {
357                $notes[] = $current->notes;
358            }
359        }
360        return $notes;
361    }
362
363    /**
364     * Get a list of lists containing this record.
365     *
366     * @param int $user_id ID of user to load tags from (null for all users)
367     *
368     * @return array
369     *
370     * @deprecated Use UserListServiceInterface::getListsContainingRecord()
371     */
372    public function getContainingLists($user_id = null)
373    {
374        return $this->getDbService(UserListServiceInterface::class)->getListsContainingRecord(
375            $this->getUniqueId(),
376            $this->getSourceIdentifier(),
377            $user_id
378        );
379    }
380
381    /**
382     * Returns true if the record supports real-time AJAX status lookups.
383     *
384     * @return bool
385     */
386    public function supportsAjaxStatus()
387    {
388        return false;
389    }
390
391    /**
392     * Checks the current record if it's supported for generating OpenURLs.
393     *
394     * @return bool
395     */
396    public function supportsOpenUrl()
397    {
398        return true;
399    }
400
401    /**
402     * Checks the current record if it's supported for generating COinS-OpenURLs.
403     *
404     * @return bool
405     */
406    public function supportsCoinsOpenUrl()
407    {
408        return true;
409    }
410
411    /**
412     * Check if rating the record is allowed.
413     *
414     * @return bool
415     */
416    public function isRatingAllowed(): bool
417    {
418        return !empty($this->recordConfig->Social->rating);
419    }
420
421    /**
422     * Store a piece of supplemental information in the record driver.
423     *
424     * @param string $key Name of stored information
425     * @param mixed  $val Information to store
426     *
427     * @return void
428     */
429    public function setExtraDetail($key, $val)
430    {
431        $this->extraDetails[$key] = $val;
432    }
433
434    /**
435     * Get an array of supported, user-activated citation formats.
436     *
437     * @return array Strings representing citation formats.
438     */
439    public function getCitationFormats()
440    {
441        $formatSetting = $this->mainConfig->Record->citation_formats ?? true;
442
443        // Default behavior: use all supported options.
444        if ($formatSetting === true || $formatSetting === 'true') {
445            return $this->getSupportedCitationFormats();
446        }
447
448        // Citations disabled:
449        if ($formatSetting === false || $formatSetting === 'false') {
450            return [];
451        }
452
453        // Filter based on include list:
454        $allowed = array_map('trim', explode(',', $formatSetting));
455        return array_intersect($allowed, $this->getSupportedCitationFormats());
456    }
457
458    /**
459     * Get an array of strings representing citation formats supported
460     * by this record's data (empty if none). For possible legal values,
461     * see /application/themes/root/helpers/Citation.php.
462     *
463     * @return array Strings representing citation formats.
464     */
465    protected function getSupportedCitationFormats()
466    {
467        return [];
468    }
469
470    /**
471     * Retrieve a piece of supplemental information stored using setExtraDetail().
472     *
473     * @param string $key Name of stored information
474     *
475     * @return mixed
476     */
477    public function getExtraDetail($key)
478    {
479        return $this->extraDetails[$key] ?? null;
480    }
481
482    /**
483     * Try to call the requested method and return null if it is unavailable; this is
484     * useful for checking for the existence of get methods for particular types of
485     * data without causing fatal errors.
486     *
487     * @param string $method  Name of method to call.
488     * @param array  $params  Array of parameters to pass to method.
489     * @param mixed  $default A default value to return if the method is not
490     * callable
491     *
492     * @return mixed
493     */
494    public function tryMethod($method, $params = [], $default = null)
495    {
496        return is_callable([$this, $method]) ? $this->$method(...$params) : $default;
497    }
498}