VuFind uses simple PHP classes called ILS drivers to communicate with external integrated library systems in order to obtain information and perform actions that are outside the scope of its own index and database. This includes things like listing a patron’s checked out items or determining whether books are currently on the shelf. In the past, VuFind’s drivers have been fairly week with regard to important patron activities like placing holds and renewing books. Several libraries have implemented local customizations to support these features, but the native support involved, at best, linking off to a page in a third-party OPAC.
With the forthcoming VuFind 1.2 release (date not yet determined, but probably late summer or early fall), all that will change. The VuFind driver model has been updated with robust support for expanded patron functionality (thanks largely to the tireless efforts of Luke O’Sullivan, who has been collaborating with me for months on this problem). The ILS Driver Specification has already been updated to reflect the new features, but since this is somewhat complicated, I thought a more narrative explanation of how the new features work might be beneficial.
This article is designed to explain exactly what you need to do to add hold, recall and renewal functionality to your ILS driver. It will also touch on some of the infrastructure changes in VuFind needed to support these new features, and some general best practices for extending drivers. As always, if you want more detail on anything, you are free to contact me through comments on the blog or the VuFind mailing lists.
One of the complicated things about implementing a generic system for dealing with things like holds and renewals is that different systems have different capabilities and rely on different data in order to achieve these actions. Our design tries to keep as much logic inside the ILS driver as possible. VuFind interacts with the driver in two key ways:
• It queries the driver (by checking for the existence of certain methods and/or using the getConfig method) to determine which features are available. Unsupported capabilities will simply be hidden from the end user.
• It tries to feed the driver with its own data as much as possible. In many cases, the inputs to some methods are outputs from other methods. VuFind makes no assumptions about the contents of the data — it just pushes it to the appropriate places. Associative arrays and delimited strings are the driver author’s friends — these can be used to encapsulate whatever data the driver needs, and VuFind will make sure they end up in the right places. This should all become more clear when you see some examples below!
The Least Common Denominator
As mentioned earlier, the simplest way to support advanced ILS features is to simply link to the ILS’ native OPAC. This does not generally provide a good user experience, but sometimes it is the only option. There are several methods you can implement if you want (or need) to settle for this minimal level of functionality:
While getHoldLink has been around for a long time, the other two methods are new… and both of them demonstrate the “driver using its own data” principle discussed above. getCancelHoldLink is fed with an entry from the array returned by getMyHolds, while getRenewLink is similarly fed from getMyTransactions. This is very convenient: when you’re retrieving information from the ILS about current holds or checkouts, it’s easy enough to pull whatever details are needed to link to the system’s OPAC… then you simply assemble it into a URL in the getLink method and you’re done!
Placing Holds Inside VuFind
Obviously, the ideal solution is not linking to a legacy system; it’s filling out a form within VuFind itself. Fortunately, this is now achievable. It requires a few methods to be implemented:
• getConfig – Before offering holds functionality, VuFind will call the getConfig method with a parameter of “Holds”. As the driver spec describes in more detail, the method needs to return an associative array containing entries VuFind uses to render the hold form correctly. It is up to you whether to hard-code these values in your ILS driver or pass them along from the driver’s .ini file. The most critical key is the “HMACKeys” value, which tells VuFind which form fields to use in generating an HMAC message authentication code that helps prevent users from placing holds on items that they are not supposed to. If you omit HMACKeys, VuFind will assume that native holds are disabled and will fail over to the getHoldLink approach.
• getHolding – Chances are you already have a getHolding method in your driver, but you may need to augment it with some extra fields in the return array if you need extra data to place holds (for example, a “hold” vs. “recall” status, or an item ID in place of a bib ID). Fortunately, you can include any field of the getHolding return array as part of getConfig’s HMACKeys list in order to ensure that it is passed along to the placeHold method below. This allows you to pass any or all necessary data without VuFind having to know exactly what is needed! If possible, you should also make sure that your getHolding array includes the “addlink” key indicating whether or not the current user is allowed to place a hold on the current item — this key makes it possible to use the “driver” option in config.ini’s Catalog:holds_mode setting, which is usually the smartest way for VuFind to present links.
• placeHold – This method receives an associative array containing patron information from patronLogin along with whatever hold form fields were activated through the settings returned by getConfig. It is responsible for actually placing the hold and then returning a success or failure status.
There are quite a few small details to line up here, but the important thing is that the driver specifies what data is needed, provides all of that data, and then uses it to place the hold. All VuFind does is pass the messages from one place to another!
…and the rest
If you understand how holds work, the other new features are very similar, only slightly less complicated. A quick summary:
• To cancel holds, implement getCancelHoldDetails (which generates an identifier string using data passed to it from getMyHolds) and cancelHolds (which actually cancels holds based on patron data and an array of strings generated by getCancelHoldDetails).
• To renew items, implement getRenewDetails (which generates an identifier string using data passed to it from getMyTransactions) and renewMyItems (which actually renews items based on patron data and an array of strings generated by getRenewDetails). Also be sure that getMyTransactions includes an appropriate “renewable” key in its return array.
A Final Word on Object Orientation
That covers how to make holds work… but there’s one more detail that may affect driver authors. It is often the case that an ILS requires a version upgrade or a for-pay API plug-in to support these advanced features. In these situations, some users may want the full functionality, while others may require a more stripped-down version that only supports basic features. This is certainly the case for Voyager, where Voyager 6 users will have to settle for the old getHoldLink functionality while Voyager 7 users may have access to a RESTful API that allows every imaginable bell and whistle. Fortunately, PHP’s object-oriented model offers a simple solution: implement minimal functionality as a base class, then override and add methods in a child class to expand functionality.
The Voyager.php and VoyagerRestful.php drivers are an example of this technique in action. Similar work has been done for Horizon users with and without access to its XML API.
One useful design pattern you may notice if you look at the code for these existing drivers is that large chunks of key methods have been broken out into support methods: one that generates SQL in an abstracted associative array format and one that processes the database response. This makes it relatively easy for a child class to inject a couple of new fields into a query or process data slightly differently without having to copy and paste a large, complex method from the parent class. This design pattern is not only useful for implementing holds functionality; it’s also very handy for making minor local customizations to drivers.