Introduction

In the first post of this series, we covered the necessary steps to populate VuFind’s Solr cores with title and authority records. This second post describes changes to configuration files, as well as modifications that are necessary to interact, display and export records. All our customizations are based on existing VuFind code and, to ensure maintainability, stored in the local/ folder and the custom module Fiddk. We want to remind the reader, that we assume basic VuFind knowledge, which can be acquired from the documentation.

Customizing Record Views

Record views are very customizable in VuFind. A good entry point for record views is to check out VuFind’s “Customizing Record Views” video. The following subsections explain the steps we took to make our record fields visible in the correct format.

Record Driver

In VuFind, indexed records are accessible only via so-called RecordDrivers – PHP objects which are wrapped around the metadata. There is no direct interaction with the data which allows for very generic and flexible code. This code can be reused across metadata formats, due to traits and inheritance in PHP. To be precise, there is the DefaultRecord class that inherits general RecordDriver functionalities from the AbstractBase and implements basic record-related behavior, regardless of the underlying search backend. SolrDefault inherits from DefaultRecord and provides additional methods for Solr-related details, like search settings, highlighting and hierarchies. If you wanted to change anything in regard to MARC21 records, you would extend from the SolrMarc driver, which provides MARC21 specific code. Similar to the SolrMarc driver, we add custom methods for EDM in a SolrEdm driver that inherits from SolrDefault (see the full inheritance tree with all added classes in Fig. 1). Note that the class name is dependent on the name you chose in Solr’s record_format field.

Schema of `RecordDriver` inheritance. It roots from the `RecordDriver` AbstractBase to DefaultRecord to SolrDefault. From there SolrMarc, SolrEdm, and SolrAuthDefault inherit methods. SolrWork inherits from SolrEdm, while SolrAuthor and SolrEvent derive from SolrAuthDefault.

(Fig. 1: Schema of `RecordDriver` inheritance with relevant classes of this post. Custom classes are highlighted.)

If you want to extend existing RecordDrivers, you can also use VuFind’s code generators like in this example for SolrAuthDefault:

1
$VUFIND_HOME php public/index.php generate extendclass --extendfactory VuFind\\RecordDriver\\SolrAuthDefault Fiddk

The newly generated class lives in the namespace of our custom module, Fiddk in our case. The parent classes already come with a standard set of getters for retrieving common sorts of data elements (title, author, subject, etc.) and display them. There is not much need for changes if you do not plan to go wild and keep close to the default schema.xml as recommended in the first post of this series. If you want to change the behavior or extend the functionality though, you can of course do so. We can e.g. extend VuFind’s SolrAuthDefault driver and implement a custom get method for retrieving the authority type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace Fiddk\RecordDriver;

class SolrAuthDefault extends \VuFind\RecordDriver\SolrAuthDefault
{
    use EdmReaderTrait;

    public function getAuthType()
    {
        return $this->fields['record_type'] ?? '';
    }
    ...

The class above makes use of its direct access to Solr fields via $this->fields. You only need to be aware of the return value of the direct access, which is an array for multi-valued Solr fields and a string for single-valued fields. In a similar fashion, we can add custom functionality for EDM records in the SolrEdm driver, such as parsing EDM records, in case we want to display fields that are not part of the Solr index or getting related EDM event records.

Additionally, a RecordDriver comes with a set of templates for rendering each record in the record result list or on detail pages. This is especially helpful when you have a variety of different record formats that you want to display in one combined result list. PHP’s duck typing approach helps to determine compatibility between specific records and features. So data is only provided and displayed under certain conditions. For a more consistent user experience, it is recommended to use as many existing templates as possible that are shared between several RecordDrivers. If you don’t like the style, we recommend to apply changes via CSS.

You can either choose to call RecordDriver methods directly or render custom templates. For our approach, we add custom templates and snippets into our theme folder and configure the usage with the RecordDataFormatter.

Record Data Formatter

Since VuFind 4.0, the view helper RecordDataFormatter exists. It is a configuration-driven approach for displaying tabular data coming from RecordDriver objects and replaces long and confusing HTML templates in a flexible and more maintainable way. It needs the RecordDriver object as input, as well as a specification describing the fields of the output table and how to retrieve the table data. VuFind ships with stored specifications for collection-info, collection-record, core and description, which are used in the corresponding .phtml files in the record driver template directory.

As detailed above, you can extend the given RecordDataFormatterFactory with a code generator in VuFind and modify it to your needs or create new specification arrays with the SpecBuilder. In order to overwrite the behavior of the core display, thanks to inheritance, you can initialize the SpecBuilder in your local folder with parent::detDefaultCoreSpecs() to receive all functionality from the parent version. You can then make changes to the order of fields or add new record fields easily.

The easiest way to add a new field, is by adding a setLine(str, str) with the name of the field and the record driver method to call to get the data from the RecordDriver. It defaults to a simple render type and prints the data that results from the called method as is. Remember to translate field names in your local language files because they are displayed raw otherwise.

1
$spec->setLine('dcterms:extent', 'getExtent');

The method can also be called with options like the possibility to translate the results of the called method.

1
$spec->setLine('dc:language', 'getLanguages', null, ['translate'=> true, 'translationTextDomain' => 'iso639-3::']);

You can also provide a template file name as input for more complex cases like displaying related events as links to the authority record with the event date in parentheses.

1
$spec->setTemplateLine('edm:wasPresentAt', 'getEvents', 'link-event.phtml');

The result of this function call displays our newly created Solr fields from the first post as:

Display of related performance title and date.

(Fig. 2: Display of related performance title and date.)

Configuration

If you used code generators in the above workflow, you might not need to do anything further to make VuFind aware of the new code. Otherwise, we need to add the RecordDrivers to the PluginManager for RecordDrivers, which is part of the module.config.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class PluginManager extends \VuFind\RecordDriver\PluginManager
{
    protected $aliases = [
        'solredm' => SolrEdm::class,
        ...
    ];

    protected $factories = [
        SolrEdm::class => \VuFind\RecordDriver\SolrDefaultFactory::class,
        ...
    ];
}

… and add our newly created helper factory RecordDataFormatterFactory in theme.config.php.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
return [ ...
    'helpers' => [
      'factories' => [
        'Fiddk\View\Helper\Fiddk\RecordDataFormatter' => 'Fiddk\View\Helper\Fiddk\RecordDataFormatterFactory',
      ],
      'aliases' => [ // Overrides
        'VuFind\View\Helper\Root\RecordDataFormatter' => 'Fiddk\View\Helper\Fiddk\RecordDataFormatter',
      ],
    ],
];

If you want to do minor changes to the search behavior and record display, you can also play around and make adjustments in the well documented configuration files config.ini, searchspecs.yaml, searches.ini and facets.ini in your local/ folder copies. For example, if you have a hierarchical structure in your metadata and populated the corresponding Solr fields, you can switch on simpleContainerLinks in the [Hierarchy] section of your local config.ini. Or if you want to add a new facet that is based on a newly introduced Solr field, you need to add it in the [Results] section of facets.ini. Though don’t forget to delete the configuration cache when you want to see the changes outside of development mode. For a deep dive, it is recommended to check out the configuration documentation.

For the sake of completeness, we are also providing the user with different search entry points (see Fig. 3), dependening on what record type (title or authority data) they are searching for. However, this part would deserve its own post and is not covered here. It needs more complex adjustments to the search backend, controllers and routing and is not necessarily required for the introduction of new metadata formats.

The FID offers 4 different search entries.

(Fig. 3: The search portal offers 4 different search entries (resources, agents, events and works).)

Exporting records

Records can be exported in the new EDM format by defining a method getXML() for it in an additional EDM parsing trait EDMReadingTrait. It is comparable to the getRDFXML() method in VuFind’s MarcAdvancedTrait and simply outputs the record object as XML. In a local export.ini, we name our export format, add the required methods and define e.g. the content type.

1
2
3
[Edm]
requiredMethods[] = getXML
headers[] = "Content-type: application/rdf+xml"

EDM then needs to be added in the [Export] section of config.ini where you can decide if you want to allow batch (“bulk”) exports, single-record exports or both.

Conclusion

Since the introduction of RecordDrivers and the RecordDataFormatter in VuFind, the abstraction and customization of record views has become a rather straight forward process. Even if you want to use new metadata formats, you can still get very far with the existing VuFind code while keeping your own customizations modular and maintainable.