Link Digital Object Handle - URL to reference and thumbnail images rather than Browse upload

125 views
Skip to first unread message

Vicky Phillips

unread,
Jul 7, 2023, 5:12:29 AM7/7/23
to AtoM Users
Hi All,
We're currently on version 2.6 of AtoM and are looking at linking to our handles for our digital objects which have been ingested to Fedora, which in turn point to the Universal Viewer e.g. http://hdl.handle.net/10107/4763941

We've used the Link digital object under More button to add the handle to the Url box e.g. http://hdl.handle.net/10107/4766307 however we don't want to be manually uploading Reference and Thumbnail images for all of these by hand.  As we have IIIF manifests I was wondering is there a way in which we can point to Reference and Thumbnail images externally using a Url (which ends in .jpg)? If so what should be the recommended pixel width these should be, as we are able to adjust the Url accordingly?  Also is there a way of getting the link for the digital object to open in a new screen rather than in the current screen? Would be great to hear if others have managed to do this.
Thanks
Vicky
Digital Standards Manager
National Library of Wales

Dan Gillean

unread,
Jul 12, 2023, 9:48:04 AM7/12/23
to ica-ato...@googlegroups.com
Hi Vicky, 

AtoM is not currently equipped to handle external reference and thumbnail images, so without some extensive code modifications I can't think of how you can tell the application to follow a URL, fetch a copy, and display that instead of generating the derivatives locally as the application generally expects. 

I will also ask our team about the tab issue. I tested, and it IS the default behavior to open images in a new tab when the digital objects are loaded as expected - i.e. when either a local upload or a URL ending in a supported file extension is used, then clicking on the locally created reference image will open the master in a new tab. This doesn't seem to be the case when external URLs without a file extension are used - I found a similar use case to yours in the forum here, for example: 
I will ask our devs if they have further suggestions on how to customize your links to open externally, and will update the thread if I learn anything. However, after some initial searching, while I can see how to do it when using HTML, it's less clear if there's a universally supported URL parameter you could add to the link itself that would achieve the desired result, and as far as I'm aware there is no global configurable setting for link behavior in AtoM, so... we may be out of luck for now! 

Cheers, 

Dan Gillean, MAS, MLIS
AtoM Program Manager
Artefactual Systems, Inc.
604-527-2056
@accesstomemory
he / him


--
You received this message because you are subscribed to the Google Groups "AtoM Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ica-atom-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ica-atom-users/fbbb6c4a-897f-40e7-8064-3239323b307cn%40googlegroups.com.

Vicky Phillips

unread,
Jul 13, 2023, 4:25:44 AM7/13/23
to AtoM Users
Thanks Dan for your response. I had seen the post you mentioned, but as it was a couple of years old now I thought I'd check to see if anything has changed in the meantime.  The opening up in a new tab isn't a major issue, I'm sure users will just learn to right-click and use Open Link in New Tab within the browser. I will see if Trinity College Cambridge are able to let us know how they went about getting their Reference and Thumbnail images in to their site though, are you able to tell from the front-end?  I'm guessing they've added them manually, but it would be nice to get this confirmed. Thanks as always for your help.
Vicky

Jenny A'Brook

unread,
Aug 1, 2023, 5:43:36 AM8/1/23
to AtoM Users
Hi All,

I'm Vicky's colleague. I've been looking into this problem and have found a way to generate derivatives automatically if supplying a handle as the url when linking a digital object through the AtoM browser. This isn't what was originally requested, but removes the extra task of manually uploading derivatives when supplying handles as digital object paths.

(We use handles for the digital object path because these point to our Universal Viewer)

It required some modifications to the file lib/model/QubitDigitalObject.php, the creation of a new helper file (to handle iiif and Fedora queries), and a new config file for our Fedora credentials
In QubitDigitalObject.importFromURI:
$filename = $this->getFilenameFromUri($uri); returns our pid for this object
eg http://hdl.handle.net/10107/4763941 returns 4763941.
The new helper file creates the image request uri for this pid eg http://dams.llgc.org.uk/iiif/2.0/image/4763941/full/300,/0/default.jpg.
It tests this pid with a curl get request.
If it's a 2-level object this will return a 404, in which case the Fedora METS datastream for this object is accessed and the handle for its 1st child object is returned, and the image request uri created from this pid is used.
If it returns 200, this is a single level object and this image request uri is the correct one.
The image request uri is then used instead of the original handle uri in the call to $this->getFilenameFromUri (the filename is always default.jpg but this means the mime-type is set to image/jpeg which is important for thumbnail creation) and $this->downloadExternalObject
The image request uri is also used instead of the object's path in QubitDigitalObject.getLocalPath and
$this->getFilenameFromUri and $this->downloadExternalObject.

These modifications also work with the command-line tool 'php symfony digitalobject:regen-derivatives'

Dan Gillean

unread,
Aug 1, 2023, 8:13:12 AM8/1/23
to ica-ato...@googlegroups.com
Hi Jenny, 

Very cool, thanks for sharing! I'm glad to hear that you've found a solution that will work for your needs. 

Is there a gist of the code somewhere, for users with a similar use case to look at and modify locally?

Cheers, 

Dan Gillean, MAS, MLIS
AtoM Program Manager
Artefactual Systems, Inc.
604-527-2056
@accesstomemory
he / him

Jenny A'Brook

unread,
Aug 2, 2023, 6:24:52 AM8/2/23
to AtoM Users
These are the 2 functions I altered in QubitDigitalObject:

  public function importFromURI($uri, $options = array())
  {
    include_once sfConfig::get('sf_root_dir').'/lib/helper/NLWQubitHelper.php';
    $damsUri = getDerivativesPath($uri);
    $filename = $this->getFilenameFromUri($damsUri);    
    // Set general properties that don't require downloading the asset
    $this->usageId = QubitTerm::EXTERNAL_URI_ID;
    $this->name = $filename;
    $this->path = $uri;
    $this->setMimeAndMediaType();

    // If not creating derivatives right now, don't download the resource
    if (!$this->createDerivatives)
    {
      return;
    }
    // Download the remote resource bitstream
    $contents = $this->downloadExternalObject($damsUri, $options);
    $this->saveAndAttachFileContent($filename, $contents);
  }

  public function getLocalPath()
  {
    include_once sfConfig::get('sf_root_dir').'/lib/helper/NLWQubitHelper.php';
    $myPath = $this->localPath;
    $myUsage = $this->usageId;
    $llgcId = $this->getFilenameFromUri($this->path);
    if (null === $this->localPath && QubitTerm::EXTERNAL_URI_ID == $this->usageId)
    {      
      $uri = getDerivativesPath($this->path);
      $filename = $this->getFilenameFromUri($uri);;
      $contents = $this->downloadExternalObject($uri);
      $this->localPath = Qubit::saveTemporaryFile($filename, $contents);
    }
    if (null === $this->localPath && QubitTerm::EXTERNAL_FILE_ID == $this->usageId)
    {
      if (false === $contents = $this->file_get_contents_if_not_empty($this->path))
      {
        throw new sfException(sprintf('Error reading file or file is empty.', $filepath));
      }
      $this->localPath = Qubit::saveTemporaryFile($filename, $contents);
    }
    return $this->localPath;
  }
 
This is the helper method. which is probably quite specific to us but might help other Fedora users:

<?php
function getFilenameFromHandle($uri)
{
  $uriComponents = parse_url($uri);
  $filename = basename($uriComponents['path']);
  if (1 > strlen($filename))
 {
   writelog("ERROR", "Couldn't parse filename from {$uri}");
  }
  return $filename;
}

function makeRequest($ch, $expectCode) {
  $success = false;
  $result = false;
  $tryCount = 1;
  while ($success != true && $tryCount <= 10) {
      if ($tryCount > 1) {
          $preinfo = curl_getinfo($ch);
      }
      $tryCount++;
      $result = curl_exec($ch);
      $info = curl_getinfo($ch);
     
      if ($result) {
          $success = true;
          if ($info['http_code'] != $expectCode) {
              $success = false;
      writelog("ERROR", "HTTP Request returned {$info['http_code']} but expected $expectCode for URL: {$info['url']}")
              sleep($tryCount * 2);
          }
      } else {
          if ($tryCount < 10) {
              sleep($tryCount * 2);
      writelog("ERROR" ,"FAILED HTTP Request $tryCount times, retrying.");
          } else {
      writelog("FATAL" ,"FAILED HTTP Request Max retries reached. Failing permanently.");
              $result = null;
          }
      }
  }
  return ["result" => $result, "info" => $info];
}

function file_get_contents_retry($url) {
  $proxy = getProxy();
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_PROXY, $proxy);
  return makeRequest($ch, 200);
}

function testDamsPath($url){
  $curl = curl_init($url);
  curl_setopt($curl, CURLOPT_URL, $url);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  $resp = curl_exec($curl);
  $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  curl_close($curl);
  return $httpcode;
}

function getProxy() {
  return array(
      'http' => array(
          'proxy' => '**********',
      )
  );
}

function getFileUrl($documentID, $username, $password, $hostUrl, $datastream) {
  return 'http://' . $username . ':' . $password . '@' . $hostUrl . '/fedora/objects/llgc-id:' . $documentID . '/datastreams/' . $datastream . '/content';
}

function getMetsFileContents($documentID, $username, $password, $hostUrl) {
  $opts = getProxy();
  stream_context_set_default($opts);
  $context = stream_context_create($opts);
  libxml_set_streams_context($context);
  $metsLocation = getFileUrl($documentID, $username, $password, $hostUrl, "METS");
  $xmlFile = file_get_contents_retry($metsLocation);
  if ($xmlFile["info"]["http_code"] != 200) {
      $arr = array('error' => 'No document found for ID');
      http_response_code(404);
      exit;
  }
  return $xmlFile["result"];
}

function getChildHandle($xpath) {
  $file_elements = $xpath->evaluate("/METS:mets/METS:structMap/METS:div/METS:div/METS:mptr");
  if ($file_elements->length == 0) {
      $arr = array('error' => 'No child handles found');
      http_response_code(404);
      echo json_encode($arr);
      exit;
  }
  $handles = [];
  foreach($file_elements as $file_element) {
    $handles[] = $file_element->getAttribute("xlink:href");
}
  return $handles[0];
}

function getMetsXPath($metsContents) {
  $xml_doc = new DOMDocument();
  $xml_doc->loadXML($metsContents);
  $xpath = new DOMXPath($xml_doc);
  $xpath->registerNamespace("METS", "http://www.loc.gov/METS/");
  $xpath->registerNamespace("xlink", "http://www.w3.org/1999/xlink");
  $xpath->registerNamespace("premis", "info:lc/xmlns/premis-v2");
  return $xpath;
}

function getDamsPath($llgc_id){
  $dams1st = "http://dams.llgc.org.uk/iiif/2.0/image/";
  $dams2nd = "/full/300,/0/default.jpg";
  $damsPath = $dams1st.$llgc_id.$dams2nd;
  return $damsPath;
}

function getConfig() {
    $config = (require '/var/www/html/config/nlw_config.php');
    $username = $config["fedoraUsername"];
    $password = $config["fedoraPassword"];
    $hostUrl = $config["fedoraHostUrl"];
    return ["username" => $username, "password" => $password,
            "hostUrl" => $hostUrl];
}

function getDerivativesPath($handlePath){
  $llgc_id = getFilenameFromHandle($handlePath);
  $damsPath = getDamsPath($llgc_id);
  $response = testDamsPath($damsPath);
  if ($response == 200) {
    return $damsPath;
  }
  else{
    //find the 1st child of this object
    $config = getConfig();
    $metsFileContents = getMetsFileContents($llgc_id, $config["username"], $config["password"], $config["hostUrl"]);
    $xpath = getMetsXPath($metsFileContents);
    $childHandle = getChildHandle($xpath);
    $newLlgcId = substr(explode('http://hdl.handle.net/10107/',$childHandle)[1], 0, -2);
    $damsPath = getDamsPath($newLlgcId);
    return $damsPath;
  }
}
?>

Dan Gillean

unread,
Aug 2, 2023, 8:22:11 AM8/2/23
to ica-ato...@googlegroups.com
Thanks for sharing, Jenny! 

Cheers, 

Dan Gillean, MAS, MLIS
AtoM Program Manager
Artefactual Systems, Inc.
604-527-2056
@accesstomemory
he / him

Reply all
Reply to author
Forward
0 new messages