I think you should be able to handle this entirely within the ILS driver, though doing so may not be the most efficient option. The basic idea is anywhere that a hold requires an item ID, the driver can always call $this->getHolding() using the bib ID and then extract an item ID from that response.
Two possible approaches:
1.) The hacky way – if you look at HoldLogicTitle::driverHold(), you will see that VuFind asks the driver if a given hold is valid by calling the driver’s checkRequestIsValid() method. Right now, the Unicorn driver doesn’t define this method, which causes VuFind to assume that everything is valid all of the time. However, if you wanted to be clever, you could define this method and make sure that the second parameter ($data) is passed by reference rather than by value. That way, you could inject values (like item IDs) into the array inside the checkRequestIsValid() method, and these injected values would pass on down to the URL generated by HoldLogicTitle::_getHoldDetails(). Admittedly, I don’t actually like this solution – it results in potentially confusing code since the source of the modification to the $data array is decidedly non-obvious. But it might be the easiest way to modify the hold URL without touching core VuFind code.
2.) The more logical way – wait until Unicorn::placeHold is called, check the value of $holdDetails[‘level’], and load an item ID on the fly with $this->getHolding() as needed. You can always fail with an error if no item ID can be found. I think I like this solution the best: it avoids touching core code, it’s relatively easy to understand, and it avoids doing extra lookups until you know for sure that the data is actually needed. (A minor delay during a “place hold” process is unlikely to upset a user… but if displaying holdings becomes slow due to unnecessary up-front lookups, more people will be adversely affected).
Does that make sense, or am I missing something important?
Also, once you get this all sorted out, would you be willing to share a patch?