bibb-theme/templates/RecordDriver/SolrDefault/SolrDefault.php

1980 lines
58 KiB
PHP
Executable File

<?php
/**
* Default model for Solr records -- used when a more specific model based on
* the recordtype field cannot be found.
*
* PHP version 5
*
* Copyright (C) Villanova University 2010.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* @category VuFind
* @package RecordDrivers
* @author Demian Katz <demian.katz@villanova.edu>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:record_drivers Wiki
*/
namespace VuFind\RecordDriver;
use VuFindCode\ISBN, VuFind\View\Helper\Root\RecordLink;
/**
* Default model for Solr records -- used when a more specific model based on
* the recordtype field cannot be found.
*
* This should be used as the base class for all Solr-based record models.
*
* @category VuFind
* @package RecordDrivers
* @author Demian Katz <demian.katz@villanova.edu>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:record_drivers Wiki
*
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
*/
class SolrDefault extends AbstractBase
{
/**
* These Solr fields should be used for snippets if available (listed in order
* of preference).
*
* @var array
*/
protected $preferredSnippetFields = [
'contents', 'topic_facet', 'topic'
];
/**
* These Solr fields should NEVER be used for snippets. (We exclude author
* and title because they are already covered by displayed fields; we exclude
* spelling because it contains lots of fields jammed together and may cause
* glitchy output; we exclude ID because random numbers are not helpful).
*
* @var array
*/
protected $forbiddenSnippetFields = [
'author', 'title', 'title_short', 'title_full',
'title_full_unstemmed', 'title_auth', 'title_sub', 'spelling', 'id',
'ctrlnum', 'author_variant', 'author2_variant'
];
/**
* These are captions corresponding with Solr fields for use when displaying
* snippets.
*
* @var array
*/
protected $snippetCaptions = [];
/**
* Should we highlight fields in search results?
*
* @var bool
*/
protected $highlight = false;
/**
* Should we include snippets in search results?
*
* @var bool
*/
protected $snippet = false;
/**
* Hierarchy driver plugin manager
*
* @var \VuFind\Hierarchy\Driver\PluginManager
*/
protected $hierarchyDriverManager = null;
/**
* Hierarchy driver for current object
*
* @var \VuFind\Hierarchy\Driver\AbstractBase
*/
protected $hierarchyDriver = null;
/**
* Highlighting details
*
* @var array
*/
protected $highlightDetails = [];
/**
* Search results plugin manager
*
* @var \VuFindSearch\Service
*/
protected $searchService = null;
/**
* Should we use hierarchy fields for simple container-child records linking?
*
* @var bool
*/
protected $containerLinking = false;
/**
* Constructor
*
* @param \Laminas\Config\Config $mainConfig VuFind main configuration (omit for
* built-in defaults)
* @param \Laminas\Config\Config $recordConfig Record-specific configuration file
* (omit to use $mainConfig as $recordConfig)
* @param \Laminas\Config\Config $searchSettings Search-specific configuration file
*/
public function __construct($mainConfig = null, $recordConfig = null,
$searchSettings = null
) {
// Turn on highlighting/snippets as needed:
$this->highlight = !isset($searchSettings->General->highlighting)
? false : $searchSettings->General->highlighting;
$this->snippet = !isset($searchSettings->General->snippets)
? false : $searchSettings->General->snippets;
// Load snippet caption settings:
if (isset($searchSettings->Snippet_Captions)
&& count($searchSettings->Snippet_Captions) > 0
) {
foreach ($searchSettings->Snippet_Captions as $key => $value) {
$this->snippetCaptions[$key] = $value;
}
}
// Container-contents linking
$this->containerLinking
= !isset($mainConfig->Hierarchy->simpleContainerLinks)
? false : $mainConfig->Hierarchy->simpleContainerLinks;
parent::__construct($mainConfig, $recordConfig);
}
/**
* Get highlighting details from the object.
*
* @return array
*/
public function getHighlightDetails()
{
return $this->highlightDetails;
}
/**
* Add highlighting details to the object.
*
* @param array $details Details to add
*
* @return void
*/
public function setHighlightDetails($details)
{
$this->highlightDetails = $details;
}
/**
* Get access restriction notes for the record.
*
* @return array
*/
public function getAccessRestrictions()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get all subject headings associated with this record. Each heading is
* returned as an array of chunks, increasing from least specific to most
* specific.
*
* @param bool $extended Whether to return a keyed array with the following
* keys:
* - heading: the actual subject heading chunks
* - type: heading type
* - source: source vocabulary
*
* @return array
*/
public function getAllSubjectHeadings($extended = false)
{
$headings = [];
foreach (['topic', 'geographic', 'genre', 'era'] as $field) {
if (isset($this->fields[$field])) {
$headings = array_merge($headings, $this->fields[$field]);
}
}
// The Solr index doesn't currently store subject headings in a broken-down
// format, so we'll just send each value as a single chunk. Other record
// drivers (i.e. MARC) can offer this data in a more granular format.
$callback = function ($i) use ($extended) {
return $extended
? ['heading' => [$i], 'type' => '', 'source' => '']
: [$i];
};
return array_map($callback, array_unique($headings));
}
/**
* Get all record links related to the current record. Each link is returned as
* array.
* NB: to use this method you must override it.
* Format:
* <code>
* array(
* array(
* 'title' => label_for_title
* 'value' => link_name
* 'link' => link_URI
* ),
* ...
* )
* </code>
*
* @return null|array
*/
public function getAllRecordLinks()
{
return null;
}
/**
* Get Author Information with Associated Data Fields
*
* @param string $index The author index [primary, corporate, or secondary]
* used to construct a method name for retrieving author data (e.g.
* getPrimaryAuthors).
* @param array $dataFields An array of fields to used to construct method
* names for retrieving author-related data (e.g., if you pass 'role' the
* data method will be similar to getPrimaryAuthorsRoles). This value will also
* be used as a key associated with each author in the resulting data array.
*
* @return array
*/
public function getAuthorDataFields($index, $dataFields = [])
{
$data = $dataFieldValues = [];
// Collect author data
$authorMethod = sprintf('get%sAuthors', ucfirst($index));
$authors = $this->tryMethod($authorMethod, [], []);
// Collect attribute data
foreach ($dataFields as $field) {
$fieldMethod = $authorMethod . ucfirst($field) . 's';
$dataFieldValues[$field] = $this->tryMethod($fieldMethod, [], []);
}
// Match up author and attribute data (this assumes that the attribute
// arrays have the same indices as the author array; i.e. $author[$i]
// has $dataFieldValues[$attribute][$i].
foreach ($authors as $i => $author) {
if (!isset($data[$author])) {
$data[$author] = [];
}
foreach ($dataFieldValues as $field => $dataFieldValue) {
if (!empty($dataFieldValue[$i])) {
$data[$author][$field][] = $dataFieldValue[$i];
}
}
}
return $data;
}
/**
* Get award notes for the record.
*
* @return array
*/
public function getAwards()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get notes on bibliography content.
*
* @return array
*/
public function getBibliographyNotes()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get text that can be displayed to represent this record in
* breadcrumbs.
*
* @return string Breadcrumb text to represent this record.
*/
public function getBreadcrumb()
{
return $this->getShortTitle();
}
/**
* Get the first call number associated with the record (empty string if none).
*
* @return string
*/
public function getCallNumber()
{
$all = $this->getCallNumbers();
return isset($all[0]) ? $all[0] : '';
}
/**
* Get all call numbers associated with the record (empty string if none).
*
* @return array
*/
public function getCallNumbers()
{
return isset($this->fields['callnumber-raw'])
? $this->fields['callnumber-raw'] : [];
}
/**
* Return the first valid DOI found in the record (false if none).
*
* @return mixed
*/
public function getCleanDOI()
{
$field = 'doi_str_mv';
return (isset($this->fields[$field][0]) && !empty($this->fields[$field][0]))
? $this->fields[$field][0] : false;
}
/**
* Return the first valid ISBN found in the record (favoring ISBN-10 over
* ISBN-13 when possible).
*
* @return mixed
*/
public function getCleanISBN()
{
// Get all the ISBNs and initialize the return value:
$isbns = $this->getISBNs();
$isbn13 = false;
// Loop through the ISBNs:
foreach ($isbns as $isbn) {
// Strip off any unwanted notes:
if ($pos = strpos($isbn, ' ')) {
$isbn = substr($isbn, 0, $pos);
}
// If we find an ISBN-10, return it immediately; otherwise, if we find
// an ISBN-13, save it if it is the first one encountered.
$isbnObj = new ISBN($isbn);
if ($isbn10 = $isbnObj->get10()) {
return $isbn10;
}
if (!$isbn13) {
$isbn13 = $isbnObj->get13();
}
}
return $isbn13;
}
/**
* Get just the base portion of the first listed ISSN (or false if no ISSNs).
*
* @return mixed
*/
public function getCleanISSN()
{
$issns = $this->getISSNs();
if (empty($issns)) {
return false;
}
$issn = $issns[0];
if ($pos = strpos($issn, ' ')) {
$issn = substr($issn, 0, $pos);
}
return $issn;
}
/**
* Get just the first listed OCLC Number (or false if none available).
*
* @return mixed
*/
public function getCleanOCLCNum()
{
$nums = $this->getOCLC();
return empty($nums) ? false : $nums[0];
}
/**
* Get just the first listed UPC Number (or false if none available).
*
* @return mixed
*/
public function getCleanUPC()
{
$nums = $this->getUPC();
return empty($nums) ? false : $nums[0];
}
/**
* Get the main corporate authors (if any) for the record.
*
* @return array
*/
public function getCorporateAuthors()
{
return isset($this->fields['author_corporate']) ?
$this->fields['author_corporate'] : [];
}
/**
* Get an array of all main corporate authors roles.
*
* @return array
*/
public function getCorporateAuthorsRoles()
{
return isset($this->fields['author_corporate_role']) ?
$this->fields['author_corporate_role'] : [];
}
/**
* Get the date coverage for a record which spans a period of time (i.e. a
* journal). Use getPublicationDates for publication dates of particular
* monographic items.
*
* @return array
*/
public function getDateSpan()
{
return isset($this->fields['dateSpan']) ?
$this->fields['dateSpan'] : [];
}
/**
* Deduplicate author information into associative array with main/corporate/
* secondary keys.
*
* @param array $dataFields An array of extra data fields to retrieve (see
* getAuthorDataFields)
*
* @return array
*/
public function getDeduplicatedAuthors($dataFields = ['role'])
{
$authors = [];
foreach (['primary', 'secondary', 'corporate'] as $type) {
$authors[$type] = $this->getAuthorDataFields($type, $dataFields);
}
// deduplicate
$dedup = function (&$array1, &$array2) {
if (!empty($array1) && !empty($array2)) {
$keys = array_keys($array1);
foreach ($keys as $author) {
if (isset($array2[$author])) {
$array1[$author] = array_merge(
$array1[$author],
$array2[$author]
);
unset($array2[$author]);
}
}
}
};
$dedup($authors['primary'], $authors['corporate']);
$dedup($authors['secondary'], $authors['corporate']);
$dedup($authors['primary'], $authors['secondary']);
$dedup_data = function (&$array) {
foreach ($array as $author => $data) {
foreach ($data as $field => $values) {
if (is_array($values)) {
$array[$author][$field] = array_unique($values);
}
}
}
};
$dedup_data($authors['primary']);
$dedup_data($authors['secondary']);
$dedup_data($authors['corporate']);
return $authors;
}
/**
* Get the edition of the current record.
*
* @return string
*/
public function getEdition()
{
return isset($this->fields['edition']) ?
$this->fields['edition'] : '';
}
/**
* Get notes on finding aids related to the record.
*
* @return array
*/
public function getFindingAids()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get an array of all the formats associated with the record.
*
* @return array
*/
public function getFormats()
{
return isset($this->fields['format']) ? $this->fields['format'] : [];
}
/**
* Get general notes on the record.
*
* @return array
*/
public function getGeneralNotes()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get highlighted author data, if available.
*
* @return array
*/
public function getRawAuthorHighlights()
{
// Don't check for highlighted values if highlighting is disabled:
return ($this->highlight && isset($this->highlightDetails['author']))
? $this->highlightDetails['author'] : [];
}
/**
* Get primary author information with highlights applied (if applicable)
*
* @return array
*/
public function getPrimaryAuthorsWithHighlighting()
{
$highlights = [];
// Create a map of de-highlighted valeus => highlighted values.
foreach ($this->getRawAuthorHighlights() as $current) {
$dehighlighted = str_replace(
['{{{{START_HILITE}}}}', '{{{{END_HILITE}}}}'], '', $current
);
$highlights[$dehighlighted] = $current;
}
// replace unhighlighted authors with highlighted versions where
// applicable:
$authors = [];
foreach ($this->getPrimaryAuthors() as $author) {
$authors[] = isset($highlights[$author])
? $highlights[$author] : $author;
}
return $authors;
}
/**
* Get a string representing the last date that the record was indexed.
*
* @return string
*/
public function getLastIndexed()
{
return isset($this->fields['last_indexed'])
? $this->fields['last_indexed'] : '';
}
/**
* Given a Solr field name, return an appropriate caption.
*
* @param string $field Solr field name
*
* @return mixed Caption if found, false if none available.
*/
public function getSnippetCaption($field)
{
return isset($this->snippetCaptions[$field])
? $this->snippetCaptions[$field] : false;
}
/**
* Pick one line from the highlighted text (if any) to use as a snippet.
*
* @return mixed False if no snippet found, otherwise associative array
* with 'snippet' and 'caption' keys.
*/
public function getHighlightedSnippet()
{
// Only process snippets if the setting is enabled:
if ($this->snippet) {
// First check for preferred fields:
foreach ($this->preferredSnippetFields as $current) {
if (isset($this->highlightDetails[$current][0])) {
return [
'snippet' => $this->highlightDetails[$current][0],
'caption' => $this->getSnippetCaption($current)
];
}
}
// No preferred field found, so try for a non-forbidden field:
if (isset($this->highlightDetails)
&& is_array($this->highlightDetails)
) {
foreach ($this->highlightDetails as $key => $value) {
if ($value && !in_array($key, $this->forbiddenSnippetFields)) {
return [
'snippet' => $value[0],
'caption' => $this->getSnippetCaption($key)
];
}
}
}
}
// If we got this far, no snippet was found:
return false;
}
/**
* Get a highlighted title string, if available.
*
* @return string
*/
public function getHighlightedTitle()
{
// Don't check for highlighted values if highlighting is disabled:
if (!$this->highlight) {
return '';
}
return (isset($this->highlightDetails['title'][0]))
? $this->highlightDetails['title'][0] : '';
}
/**
* Get the institutions holding the record.
*
* @return array
*/
public function getInstitutions()
{
return isset($this->fields['institution'])
? $this->fields['institution'] : [];
}
/**
* Get an array of all ISBNs associated with the record (may be empty).
*
* @return array
*/
public function getISBNs()
{
// If ISBN is in the index, it should automatically be an array... but if
// it's not set at all, we should normalize the value to an empty array.
return isset($this->fields['isbn']) && is_array($this->fields['isbn']) ?
$this->fields['isbn'] : [];
}
/**
* Get an array of all ISSNs associated with the record (may be empty).
*
* @return array
*/
public function getISSNs()
{
// If ISSN is in the index, it should automatically be an array... but if
// it's not set at all, we should normalize the value to an empty array.
return isset($this->fields['issn']) && is_array($this->fields['issn']) ?
$this->fields['issn'] : [];
}
/**
* Get an array of all the languages associated with the record.
*
* @return array
*/
public function getLanguages()
{
return isset($this->fields['language']) ?
$this->fields['language'] : [];
}
/**
* Get a LCCN, normalised according to info:lccn
*
* @return string
*/
public function getLCCN()
{
// Get LCCN from Index
$raw = isset($this->fields['lccn']) ? $this->fields['lccn'] : '';
// Remove all blanks.
$raw = preg_replace('{[ \t]+}', '', $raw);
// If there is a forward slash (/) in the string, remove it, and remove all
// characters to the right of the forward slash.
if (strpos($raw, '/') > 0) {
$tmpArray = explode("/", $raw);
$raw = $tmpArray[0];
}
/* If there is a hyphen in the string:
a. Remove it.
b. Inspect the substring following (to the right of) the (removed)
hyphen. Then (and assuming that steps 1 and 2 have been carried out):
i. All these characters should be digits, and there should be
six or less.
ii. If the length of the substring is less than 6, left-fill the
substring with zeros until the length is six.
*/
if (strpos($raw, '-') > 0) {
// haven't checked for i. above. If they aren't all digits, there is
// nothing that can be done, so might as well leave it.
$tmpArray = explode("-", $raw);
$raw = $tmpArray[0] . str_pad($tmpArray[1], 6, "0", STR_PAD_LEFT);
}
return $raw;
}
/**
* Get an array of newer titles for the record.
*
* @return array
*/
public function getNewerTitles()
{
return isset($this->fields['title_new']) ?
$this->fields['title_new'] : [];
}
/**
* Get the OCLC number(s) of the record.
*
* @return array
*/
public function getOCLC()
{
return isset($this->fields['oclc_num']) ?
$this->fields['oclc_num'] : [];
}
/**
* Support method for getOpenUrl() -- pick the OpenURL format.
*
* @return string
*/
protected function getOpenUrlFormat()
{
// If we have multiple formats, Book, Journal and Article are most
// important...
$formats = $this->getFormats();
if (in_array('Book', $formats)) {
return 'Book';
} else if (in_array('Article', $formats)) {
return 'Article';
} else if (in_array('Journal', $formats)) {
return 'Journal';
} else if (isset($formats[0])) {
return $formats[0];
} else if (strlen($this->getCleanISSN()) > 0) {
return 'Journal';
} else if (strlen($this->getCleanISBN()) > 0) {
return 'Book';
}
return 'UnknownFormat';
}
/**
* Get the COinS identifier.
*
* @return string
*/
protected function getCoinsID()
{
// Get the COinS ID -- it should be in the OpenURL section of config.ini,
// but we'll also check the COinS section for compatibility with legacy
// configurations (this moved between the RC2 and 1.0 releases).
if (isset($this->mainConfig->OpenURL->rfr_id)
&& !empty($this->mainConfig->OpenURL->rfr_id)
) {
return $this->mainConfig->OpenURL->rfr_id;
}
if (isset($this->mainConfig->COinS->identifier)
&& !empty($this->mainConfig->COinS->identifier)
) {
return $this->mainConfig->COinS->identifier;
}
return 'vufind.svn.sourceforge.net';
}
/**
* Get default OpenURL parameters.
*
* @return array
*/
protected function getDefaultOpenUrlParams()
{
// Get a representative publication date:
$pubDate = $this->getPublicationDates();
$pubDate = empty($pubDate) ? '' : $pubDate[0];
// Start an array of OpenURL parameters:
return [
'url_ver' => 'Z39.88-2004',
'ctx_ver' => 'Z39.88-2004',
'ctx_enc' => 'info:ofi/enc:UTF-8',
'rfr_id' => 'info:sid/' . $this->getCoinsID() . ':generator',
'rft.title' => $this->getTitle(),
'rft.date' => $pubDate
];
}
/**
* Get OpenURL parameters for a book.
*
* @return array
*/
protected function getBookOpenUrlParams()
{
$params = $this->getDefaultOpenUrlParams();
$params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:book';
$params['rft.genre'] = 'book';
$params['rft.btitle'] = $params['rft.title'];
$series = $this->getSeries();
if (count($series) > 0) {
// Handle both possible return formats of getSeries:
$params['rft.series'] = is_array($series[0]) ?
$series[0]['name'] : $series[0];
}
$params['rft.au'] = $this->getPrimaryAuthor();
$publishers = $this->getPublishers();
if (count($publishers) > 0) {
$params['rft.pub'] = $publishers[0];
}
$params['rft.edition'] = $this->getEdition();
$params['rft.isbn'] = (string)$this->getCleanISBN();
return $params;
}
/**
* Get OpenURL parameters for an article.
*
* @return array
*/
protected function getArticleOpenUrlParams()
{
$params = $this->getDefaultOpenUrlParams();
$params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:journal';
$params['rft.genre'] = 'article';
$params['rft.issn'] = (string)$this->getCleanISSN();
// an article may have also an ISBN:
$params['rft.isbn'] = (string)$this->getCleanISBN();
$params['rft.volume'] = $this->getContainerVolume();
$params['rft.issue'] = $this->getContainerIssue();
$params['rft.spage'] = $this->getContainerStartPage();
// unset default title -- we only want jtitle/atitle here:
unset($params['rft.title']);
$params['rft.jtitle'] = $this->getContainerTitle();
$params['rft.atitle'] = $this->getTitle();
$params['rft.au'] = $this->getPrimaryAuthor();
$params['rft.format'] = 'Article';
$langs = $this->getLanguages();
if (count($langs) > 0) {
$params['rft.language'] = $langs[0];
}
return $params;
}
/**
* Get OpenURL parameters for an unknown format.
*
* @param string $format Name of format
*
* @return array
*/
protected function getUnknownFormatOpenUrlParams($format = 'UnknownFormat')
{
$params = $this->getDefaultOpenUrlParams();
$params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:dc';
$params['rft.creator'] = $this->getPrimaryAuthor();
$publishers = $this->getPublishers();
if (count($publishers) > 0) {
$params['rft.pub'] = $publishers[0];
}
$params['rft.format'] = $format;
$langs = $this->getLanguages();
if (count($langs) > 0) {
$params['rft.language'] = $langs[0];
}
return $params;
}
/**
* Get OpenURL parameters for a journal.
*
* @return array
*/
protected function getJournalOpenUrlParams()
{
$params = $this->getUnknownFormatOpenUrlParams('Journal');
/* This is probably the most technically correct way to represent
* a journal run as an OpenURL; however, it doesn't work well with
* Zotero, so it is currently commented out -- instead, we just add
* some extra fields and to the "unknown format" case.
$params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:journal';
$params['rft.genre'] = 'journal';
$params['rft.jtitle'] = $params['rft.title'];
$params['rft.issn'] = $this->getCleanISSN();
$params['rft.au'] = $this->getPrimaryAuthor();
*/
$params['rft.issn'] = (string)$this->getCleanISSN();
// Including a date in a title-level Journal OpenURL may be too
// limiting -- in some link resolvers, it may cause the exclusion
// of databases if they do not cover the exact date provided!
unset($params['rft.date']);
// If we're working with the SFX resolver, we should add a
// special parameter to ensure that electronic holdings links
// are shown even though no specific date or issue is specified:
if (isset($this->mainConfig->OpenURL->resolver)
&& strtolower($this->mainConfig->OpenURL->resolver) == 'sfx'
) {
$params['sfx.ignore_date_threshold'] = 1;
}
return $params;
}
/**
* Get the OpenURL parameters to represent this record (useful for the
* title attribute of a COinS span tag).
*
* @param bool $overrideSupportsOpenUrl Flag to override checking
* supportsOpenUrl() (default is false)
*
* @return string OpenURL parameters.
*/
public function getOpenUrl($overrideSupportsOpenUrl = false)
{
// stop here if this record does not support OpenURLs
if (!$overrideSupportsOpenUrl && !$this->supportsOpenUrl()) {
return false;
}
// Set up parameters based on the format of the record:
$format = $this->getOpenUrlFormat();
$method = "get{$format}OpenUrlParams";
if (method_exists($this, $method)) {
$params = $this->$method();
} else {
$params = $this->getUnknownFormatOpenUrlParams($format);
}
// Assemble the URL:
return http_build_query($params);
}
/**
* Get the OpenURL parameters to represent this record for COinS even if
* supportsOpenUrl() is false for this RecordDriver.
*
* @return string OpenURL parameters.
*/
public function getCoinsOpenUrl()
{
return $this->getOpenUrl($this->supportsCoinsOpenUrl());
}
/**
* Get an array of physical descriptions of the item.
*
* @return array
*/
public function getPhysicalDescriptions()
{
return isset($this->fields['physical']) ?
$this->fields['physical'] : [];
}
/**
* Get the item's place of publication.
*
* @return array
*/
public function getPlacesOfPublication()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get an array of playing times for the record (if applicable).
*
* @return array
*/
public function getPlayingTimes()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get an array of previous titles for the record.
*
* @return array
*/
public function getPreviousTitles()
{
return isset($this->fields['title_old']) ?
$this->fields['title_old'] : [];
}
/**
* Get the main author of the record.
*
* @return string
*/
public function getPrimaryAuthor()
{
$authors = $this->getPrimaryAuthors();
return isset($authors[0]) ? $authors[0] : '';
}
/**
* Get the main authors of the record.
*
* @return array
*/
public function getPrimaryAuthors()
{
return isset($this->fields['author'])
? (array) $this->fields['author'] : [];
}
/**
* Get an array of all main authors roles (complementing
* getSecondaryAuthorsRoles()).
*
* @return array
*/
public function getPrimaryAuthorsRoles()
{
return isset($this->fields['author_role']) ?
$this->fields['author_role'] : [];
}
/**
* Get credits of people involved in production of the item.
*
* @return array
*/
public function getProductionCredits()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get the publication dates of the record. See also getDateSpan().
*
* @return array
*/
public function getPublicationDates()
{
return isset($this->fields['publishDate']) ?
$this->fields['publishDate'] : [];
}
/**
* Get human readable publication dates for display purposes (may not be suitable
* for computer processing -- use getPublicationDates() for that).
*
* @return array
*/
public function getHumanReadablePublicationDates()
{
return $this->getPublicationDates();
}
/**
* Get an array of publication detail lines combining information from
* getPublicationDates(), getPublishers() and getPlacesOfPublication().
*
* @return array
*/
public function getPublicationDetails()
{
$places = $this->getPlacesOfPublication();
$names = $this->getPublishers();
$dates = $this->getHumanReadablePublicationDates();
$i = 0;
$retval = [];
while (isset($places[$i]) || isset($names[$i]) || isset($dates[$i])) {
// Build objects to represent each set of data; these will
// transform seamlessly into strings in the view layer.
$retval[] = new Response\PublicationDetails(
isset($places[$i]) ? $places[$i] : '',
isset($names[$i]) ? $names[$i] : '',
isset($dates[$i]) ? $dates[$i] : ''
);
$i++;
}
return $retval;
}
/**
* Get an array of publication frequency information.
*
* @return array
*/
public function getPublicationFrequency()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get the publishers of the record.
*
* @return array
*/
public function getPublishers()
{
return isset($this->fields['publisher']) ?
$this->fields['publisher'] : [];
}
/**
* Get an array of information about record history, obtained in real-time
* from the ILS.
*
* @return array
*/
public function getRealTimeHistory()
{
// Not supported by the Solr index -- implement in child classes.
return [];
}
/**
* Get an array of information about record holdings, obtained in real-time
* from the ILS.
*
* @return array
*/
public function getRealTimeHoldings()
{
// Not supported by the Solr index -- implement in child classes.
return ['holdings' => []];
}
/**
* Get an array of strings describing relationships to other items.
*
* @return array
*/
public function getRelationshipNotes()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get an array of all secondary authors (complementing getPrimaryAuthors()).
*
* @return array
*/
public function getSecondaryAuthors()
{
return isset($this->fields['author2']) ?
$this->fields['author2'] : [];
}
/**
* Get an array of all secondary authors roles (complementing
* getPrimaryAuthorsRoles()).
*
* @return array
*/
public function getSecondaryAuthorsRoles()
{
return isset($this->fields['author2_role']) ?
$this->fields['author2_role'] : [];
}
/**
* Get an array of all series names containing the record. Array entries may
* be either the name string, or an associative array with 'name' and 'number'
* keys.
*
* @return array
*/
public function getSeries()
{
// Only use the contents of the series2 field if the series field is empty
if (isset($this->fields['series']) && !empty($this->fields['series'])) {
return $this->fields['series'];
}
return isset($this->fields['series2']) ?
$this->fields['series2'] : [];
}
/**
* Get the alternative title of the record.
*
* @return string
*/
public function getAlternativeTitle()
{
return isset($this->fields['title_alt']) ?
$this->fields['title_alt'] : '';
}
/**
* Get the short (pre-subtitle) title of the record.
*
* @return string
*/
public function getShortTitle()
{
return isset($this->fields['title_short']) ?
$this->fields['title_short'] : '';
}
/**
* Get the item's source.
*
* @return string
*/
public function getSource()
{
// Not supported in base class:
return '';
}
/**
* Get the subtitle of the record.
*
* @return string
*/
public function getSubtitle()
{
return isset($this->fields['title_sub']) ?
$this->fields['title_sub'] : '';
}
/**
* Get an array of technical details on the item represented by the record.
*
* @return array
*/
public function getSystemDetails()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get an array of summary strings for the record.
*
* @return array
*/
public function getSummary()
{
// We need to return an array, so if we have a description, turn it into an
// array as needed (it should be a flat string according to the default
// schema, but we might as well support the array case just to be on the safe
// side:
if (isset($this->fields['description'])
&& !empty($this->fields['description'])
) {
return is_array($this->fields['description'])
? $this->fields['description'] : [$this->fields['description']];
}
// If we got this far, no description was found:
return [];
}
/**
* Get an array of note about the record's target audience.
*
* @return array
*/
public function getTargetAudienceNotes()
{
// Not currently stored in the Solr index
return [];
}
/**
* Returns one of three things: a full URL to a thumbnail preview of the record
* if an image is available in an external system; an array of parameters to
* send to VuFind's internal cover generator if no fixed URL exists; or false
* if no thumbnail can be generated.
*
* @param string $size Size of thumbnail (small, medium or large -- small is
* default).
*
* @return string|array|bool
*/
public function getThumbnail($size = 'small')
{
if (isset($this->fields['thumbnail']) && $this->fields['thumbnail']) {
return $this->fields['thumbnail'];
}
$arr = [
'author' => mb_substr($this->getPrimaryAuthor(), 0, 300, 'utf-8'),
'callnumber' => $this->getCallNumber(),
'size' => $size,
'title' => mb_substr($this->getTitle(), 0, 300, 'utf-8'),
'recordid' => $this->getUniqueID(),
'source' => $this->getSourceIdentifier(),
];
if ($isbn = $this->getCleanISBN()) {
$arr['isbn'] = $isbn;
}
if ($issn = $this->getCleanISSN()) {
$arr['issn'] = $issn;
}
if ($oclc = $this->getCleanOCLCNum()) {
$arr['oclc'] = $oclc;
}
if ($upc = $this->getCleanUPC()) {
$arr['upc'] = $upc;
}
// If an ILS driver has injected extra details, check for IDs in there
// to fill gaps:
if ($ilsDetails = $this->getExtraDetail('ils_details')) {
foreach (['isbn', 'issn', 'oclc', 'upc'] as $key) {
if (!isset($arr[$key]) && isset($ilsDetails[$key])) {
$arr[$key] = $ilsDetails[$key];
}
}
}
return $arr;
}
/**
* Get the full title of the record.
*
* @return string
*/
public function getTitle()
{
return isset($this->fields['title']) ?
$this->fields['title'] : '';
}
/**
* Get the text of the part/section portion of the title.
*
* @return string
*/
public function getTitleSection()
{
// Not currently stored in the Solr index
return null;
}
/**
* Get the statement of responsibility that goes with the title (i.e. "by John
* Smith").
*
* @return string
*/
public function getTitleStatement()
{
// Not currently stored in the Solr index
return null;
}
/**
* Get an array of lines from the table of contents.
*
* @return array
*/
public function getTOC()
{
return isset($this->fields['contents'])
? $this->fields['contents'] : [];
}
/**
* Get hierarchical place names
*
* @return array
*/
public function getHierarchicalPlaceNames()
{
// Not currently stored in the Solr index
return [];
}
/**
* Get the UPC number(s) of the record.
*
* @return array
*/
public function getUPC()
{
return isset($this->fields['upc_str_mv']) ?
$this->fields['upc_str_mv'] : [];
}
/**
* Return an array of associative URL arrays with one or more of the following
* keys:
*
* <li>
* <ul>desc: URL description text to display (optional)</ul>
* <ul>url: fully-formed URL (required if 'route' is absent)</ul>
* <ul>route: VuFind route to build URL with (required if 'url' is absent)</ul>
* <ul>routeParams: Parameters for route (optional)</ul>
* <ul>queryString: Query params to append after building route (optional)</ul>
* </li>
*
* @return array
*/
public function getURLs()
{
// If non-empty, map internal URL array to expected return format;
// otherwise, return empty array:
if (isset($this->fields['url']) && is_array($this->fields['url'])) {
$filter = function ($url) {
return ['url' => $url];
};
return array_map($filter, $this->fields['url']);
}
return [];
}
/**
* Get a hierarchy driver appropriate to the current object. (May be false if
* disabled/unavailable).
*
* @return \VuFind\Hierarchy\Driver\AbstractBase|bool
*/
public function getHierarchyDriver()
{
if (null === $this->hierarchyDriver
&& null !== $this->hierarchyDriverManager
) {
$type = $this->getHierarchyType();
$this->hierarchyDriver = $type
? $this->hierarchyDriverManager->get($type) : false;
}
return $this->hierarchyDriver;
}
/**
* Inject a hierarchy driver plugin manager.
*
* @param \VuFind\Hierarchy\Driver\PluginManager $pm Hierarchy driver manager
*
* @return SolrDefault
*/
public function setHierarchyDriverManager(
\VuFind\Hierarchy\Driver\PluginManager $pm
) {
$this->hierarchyDriverManager = $pm;
return $this;
}
/**
* Get the hierarchy_top_id(s) associated with this item (empty if none).
*
* @return array
*/
public function getHierarchyTopID()
{
return isset($this->fields['hierarchy_top_id'])
? $this->fields['hierarchy_top_id'] : [];
}
/**
* Get the absolute parent title(s) associated with this item (empty if none).
*
* @return array
*/
public function getHierarchyTopTitle()
{
return isset($this->fields['hierarchy_top_title'])
? $this->fields['hierarchy_top_title'] : [];
}
/**
* Get an associative array (id => title) of collections containing this record.
*
* @return array
*/
public function getContainingCollections()
{
// If collections are disabled or this record is not part of a hierarchy, go
// no further....
if (!isset($this->mainConfig->Collections->collections)
|| !$this->mainConfig->Collections->collections
|| !($hierarchyDriver = $this->getHierarchyDriver())
) {
return false;
}
// Initialize some variables needed within the switch below:
$isCollection = $this->isCollection();
$titles = $ids = [];
// Check config setting for what constitutes a collection, act accordingly:
switch ($hierarchyDriver->getCollectionLinkType()) {
case 'All':
if (isset($this->fields['hierarchy_parent_title'])
&& isset($this->fields['hierarchy_parent_id'])
) {
$titles = $this->fields['hierarchy_parent_title'];
$ids = $this->fields['hierarchy_parent_id'];
}
break;
case 'Top':
if (isset($this->fields['hierarchy_top_title'])
&& isset($this->fields['hierarchy_top_id'])
) {
foreach ($this->fields['hierarchy_top_id'] as $i => $topId) {
// Don't mark an item as its own parent -- filter out parent
// collections whose IDs match that of the current collection.
if (!$isCollection
|| $topId !== $this->fields['is_hierarchy_id']
) {
$ids[] = $topId;
$titles[] = $this->fields['hierarchy_top_title'][$i];
}
}
}
break;
}
// Map the titles and IDs to a useful format:
$c = count($ids);
$retVal = [];
for ($i = 0; $i < $c; $i++) {
$retVal[$ids[$i]] = $titles[$i];
}
return $retVal;
}
/**
* Get the value of whether or not this is a collection level record
*
* NOTE: \VuFind\Hierarchy\TreeDataFormatter\AbstractBase::isCollection()
* duplicates some of this logic.
*
* @return bool
*/
public function isCollection()
{
if (!($hierarchyDriver = $this->getHierarchyDriver())) {
// Not a hierarchy type record
return false;
}
// Check config setting for what constitutes a collection
switch ($hierarchyDriver->getCollectionLinkType()) {
case 'All':
return (isset($this->fields['is_hierarchy_id']));
case 'Top':
return isset($this->fields['is_hierarchy_title'])
&& isset($this->fields['is_hierarchy_id'])
&& in_array(
$this->fields['is_hierarchy_id'],
$this->fields['hierarchy_top_id']
);
default:
// Default to not be a collection level record
return false;
}
}
/**
* Get a list of hierarchy trees containing this record.
*
* @param string $hierarchyID The hierarchy to get the tree for
*
* @return mixed An associative array of hierarchy trees on success
* (id => title), false if no hierarchies found
*/
public function getHierarchyTrees($hierarchyID = false)
{
$hierarchyDriver = $this->getHierarchyDriver();
if ($hierarchyDriver && $hierarchyDriver->showTree()) {
return $hierarchyDriver->getTreeRenderer($this)
->getTreeList($hierarchyID);
}
return false;
}
/**
* Get the Hierarchy Type (false if none)
*
* @return string|bool
*/
public function getHierarchyType()
{
if (isset($this->fields['hierarchy_top_id'])) {
$hierarchyType = isset($this->fields['hierarchytype'])
? $this->fields['hierarchytype'] : false;
if (!$hierarchyType) {
$hierarchyType = isset($this->mainConfig->Hierarchy->driver)
? $this->mainConfig->Hierarchy->driver : false;
}
return $hierarchyType;
}
return false;
}
/**
* Return the unique identifier of this record within the Solr index;
* useful for retrieving additional information (like tags and user
* comments) from the external MySQL database.
*
* @return string Unique identifier.
*/
public function getUniqueID()
{
if (!isset($this->fields['id'])) {
throw new \Exception('ID not set!');
}
return $this->fields['id'];
}
/**
* Return an XML representation of the record using the specified format.
* Return false if the format is unsupported.
*
* @param string $format Name of format to use (corresponds with OAI-PMH
* metadataPrefix parameter).
* @param string $baseUrl Base URL of host containing VuFind (optional;
* may be used to inject record URLs into XML when appropriate).
* @param RecordLink $recordLink Record link helper (optional; may be used to
* inject record URLs into XML when appropriate).
*
* @return mixed XML, or false if format unsupported.
*/
public function getXML($format, $baseUrl = null, $recordLink = null)
{
// For OAI-PMH Dublin Core, produce the necessary XML:
if ($format == 'oai_dc') {
$dc = 'http://purl.org/dc/elements/1.1/';
$xml = new \SimpleXMLElement(
'<oai_dc:dc '
. 'xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" '
. 'xmlns:dc="' . $dc . '" '
. 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
. 'xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/ '
. 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd" />'
);
$xml->addChild('title', htmlspecialchars($this->getTitle()), $dc);
$authors = $this->getDeduplicatedAuthors();
foreach ($authors as $list) {
foreach ((array)$list as $author) {
$xml->addChild('creator', htmlspecialchars($author), $dc);
}
}
foreach ($this->getLanguages() as $lang) {
$xml->addChild('language', htmlspecialchars($lang), $dc);
}
foreach ($this->getPublishers() as $pub) {
$xml->addChild('publisher', htmlspecialchars($pub), $dc);
}
foreach ($this->getPublicationDates() as $date) {
$xml->addChild('date', htmlspecialchars($date), $dc);
}
foreach ($this->getAllSubjectHeadings() as $subj) {
$xml->addChild(
'subject', htmlspecialchars(implode(' -- ', $subj)), $dc
);
}
if (null !== $baseUrl && null !== $recordLink) {
$url = $baseUrl . $recordLink->getUrl($this);
$xml->addChild('identifier', $url, $dc);
}
return $xml->asXml();
}
// Unsupported format:
return false;
}
/**
* Get an array of strings representing citation formats supported
* by this record's data (empty if none). For possible legal values,
* see /application/themes/root/helpers/Citation.php, getCitation()
* method.
*
* @return array Strings representing citation formats.
*/
protected function getSupportedCitationFormats()
{
// RKE: return new Citation Format "BIBB"
//return ['APA', 'Chicago', 'MLA'];
return ['BIBB','APA'];
}
/**
* Get the title of the item that contains this record (i.e. MARC 773s of a
* journal).
*
* @return string
*/
public function getContainerTitle()
{
return isset($this->fields['container_title'])
? $this->fields['container_title'] : '';
}
/**
* Get the volume of the item that contains this record (i.e. MARC 773v of a
* journal).
*
* @return string
*/
public function getContainerVolume()
{
return isset($this->fields['container_volume'])
? $this->fields['container_volume'] : '';
}
/**
* Get the issue of the item that contains this record (i.e. MARC 773l of a
* journal).
*
* @return string
*/
public function getContainerIssue()
{
return isset($this->fields['container_issue'])
? $this->fields['container_issue'] : '';
}
/**
* Get the start page of the item that contains this record (i.e. MARC 773q of a
* journal).
*
* @return string
*/
public function getContainerStartPage()
{
return isset($this->fields['container_start_page'])
? $this->fields['container_start_page'] : '';
}
/**
* Get the end page of the item that contains this record.
*
* @return string
*/
public function getContainerEndPage()
{
// not currently supported by Solr index:
return '';
}
/**
* Get a full, free-form reference to the context of the item that contains this
* record (i.e. volume, year, issue, pages).
*
* @return string
*/
public function getContainerReference()
{
return isset($this->fields['container_reference'])
? $this->fields['container_reference'] : '';
}
/**
* Get a sortable title for the record (i.e. no leading articles).
*
* @return string
*/
public function getSortTitle()
{
return isset($this->fields['title_sort'])
? $this->fields['title_sort'] : parent::getSortTitle();
}
/**
* Get schema.org type mapping, an array of sub-types of
* http://schema.org/CreativeWork, defaulting to CreativeWork
* itself if nothing else matches.
*
* @return array
*/
public function getSchemaOrgFormatsArray()
{
$types = [];
foreach ($this->getFormats() as $format) {
switch ($format) {
case 'Book':
case 'eBook':
$types['Book'] = 1;
break;
case 'Video':
case 'VHS':
$types['Movie'] = 1;
break;
case 'Photo':
$types['Photograph'] = 1;
break;
case 'Map':
$types['Map'] = 1;
break;
case 'Audio':
$types['MusicAlbum'] = 1;
break;
default:
$types['CreativeWork'] = 1;
}
}
return array_keys($types);
}
/**
* Get schema.org type mapping, expected to be a space-delimited string of
* sub-types of http://schema.org/CreativeWork, defaulting to CreativeWork
* itself if nothing else matches.
*
* @return string
*/
public function getSchemaOrgFormats()
{
return implode(' ', $this->getSchemaOrgFormatsArray());
}
/**
* Get information on records deduplicated with this one
*
* @return array Array keyed by source id containing record id
*/
public function getDedupData()
{
return isset($this->fields['dedup_data'])
? $this->fields['dedup_data']
: [];
}
/**
* Attach a Search Results Plugin Manager connection and related logic to
* the driver
*
* @param \VuFindSearch\Service $service Search Service Manager
*
* @return void
*/
public function attachSearchService(\VuFindSearch\Service $service)
{
$this->searchService = $service;
}
/**
* Get the number of child records belonging to this record
*
* @return int Number of records
*/
public function getChildRecordCount()
{
// Shortcut: if this record is not the top record, let's not find out the
// count. This assumes that contained records cannot contain more records.
if (!$this->containerLinking
|| empty($this->fields['is_hierarchy_id'])
|| null === $this->searchService
) {
return 0;
}
$safeId = addcslashes($this->fields['is_hierarchy_id'], '"');
$query = new \VuFindSearch\Query\Query(
'hierarchy_parent_id:"' . $safeId . '"'
);
// Disable highlighting for efficiency; not needed here:
$params = new \VuFindSearch\ParamBag(['hl' => ['false']]);
return $this->searchService->search('Solr', $query, 0, 0, $params)
->getTotal();
}
/**
* Get the container record id.
*
* @return string Container record id (empty string if none)
*/
public function getContainerRecordID()
{
return $this->containerLinking
&& !empty($this->fields['hierarchy_parent_id'])
? $this->fields['hierarchy_parent_id'][0] : '';
}
/**
* Get the bbox-geo variable.
*
* @return array
*/
public function getGeoLocation()
{
return isset($this->fields['long_lat'])
? $this->fields['long_lat'] : [];
}
/**
* Get the map display (lat/lon) coordinates
*
* @return array
*/
public function getDisplayCoordinates()
{
return isset($this->fields['long_lat_display'])
? $this->fields['long_lat_display'] : [];
}
/**
* Get the map display (lat/lon) labels
*
* @return array
*/
public function getCoordinateLabels()
{
return isset($this->fields['long_lat_label'])
? $this->fields['long_lat_label'] : [];
}
}