1433 lines
61 KiB
PHP
1433 lines
61 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Simple class for IIIF API
|
||
*
|
||
* @internal
|
||
*/
|
||
final class IIIFRequest
|
||
{
|
||
public string $rootlevel;
|
||
public string $rooturl;
|
||
public string $rootimageurl;
|
||
public int $identifier_field;
|
||
public int $description_field;
|
||
public int $sequence_field;
|
||
public string $iiif_sequence_prefix;
|
||
public int $license_field;
|
||
public string $rights_statement;
|
||
public int $title_field;
|
||
public int $max_width;
|
||
public int $max_height;
|
||
public bool $custom_sizes;
|
||
public bool $preview_tiles;
|
||
public int $preview_tile_size;
|
||
public array $preview_tile_scale_factors;
|
||
public array $media_extensions;
|
||
public int $download_chunk_size;
|
||
public array $data;
|
||
public array $headers;
|
||
public array $errors;
|
||
public int $errorcode;
|
||
public array $searchresults;
|
||
public array $processing;
|
||
public bool $only_power_of_two_sizes;
|
||
|
||
private array $response;
|
||
private array $request;
|
||
private bool $validrequest;
|
||
private int $imagewidth;
|
||
private int $imageheight;
|
||
private int $getwidth;
|
||
private int $getheight;
|
||
private int $regionx;
|
||
private int $regiony;
|
||
private int $regionw;
|
||
private int $regionh;
|
||
|
||
public function __construct($iiif_options)
|
||
{
|
||
foreach ($iiif_options as $key => $value) {
|
||
if (property_exists($this, $key)) {
|
||
$this->$key = $value;
|
||
}
|
||
}
|
||
$this->response = [];
|
||
$this->validrequest = false;
|
||
$this->headers = [];
|
||
$this->errors = [];
|
||
}
|
||
|
||
/**
|
||
* Get the IIIF response
|
||
*
|
||
* @return array
|
||
*
|
||
*/
|
||
public function getResponse(string $element = ""): array
|
||
{
|
||
return ($element != "" && isset($this->response[$element])) ? $this->response[$element] : $this->response;
|
||
}
|
||
|
||
/**
|
||
* Return information from the request
|
||
*
|
||
* @param string $element
|
||
*
|
||
* @return mixed
|
||
*
|
||
*/
|
||
public function getRequest(string $element = "")
|
||
{
|
||
return ($element != "" && isset($this->request[$element])) ? $this->request[$element] : $this->request;
|
||
}
|
||
|
||
/**
|
||
* Is the current request valid?
|
||
*
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function isValidRequest()
|
||
{
|
||
return $this->validrequest;
|
||
}
|
||
|
||
/**
|
||
* Send the IIIF information document
|
||
*
|
||
*/
|
||
public function infodoc()
|
||
{
|
||
$this->response["@context"] = "http://iiif.io/api/presentation/2/context.json";
|
||
$this->response["id"] = $this->rooturl;
|
||
$this->response["type"] = "sc:Manifest";
|
||
$arr_langdefault = i18n_get_all_translations("iiif");
|
||
foreach ($arr_langdefault as $langcode => $langdefault) {
|
||
$this->response["label"][$langcode] = [$langdefault];
|
||
}
|
||
$this->response["width"] = 6000;
|
||
$this->response["height"] = 4000;
|
||
$this->response["tiles"] = array();
|
||
$this->response["tiles"][] = array("width" => $this->preview_tile_size, "height" => $this->preview_tile_size, "scaleFactors" => $this->preview_tile_scale_factors);
|
||
$this->response["profile"] = array("http://iiif.io/api/image/3/level0.json");
|
||
$this->validrequest = true;
|
||
}
|
||
|
||
/**
|
||
* Extract IIIF request details from the URL path
|
||
*
|
||
* @param string $url The requested URL
|
||
*
|
||
* @return void
|
||
*
|
||
*/
|
||
public function parseUrl($url): void
|
||
{
|
||
$this->request = [];
|
||
|
||
$request_url = strtok($url, '?');
|
||
$path = substr($request_url, strpos($request_url, $this->rootlevel) + strlen($this->rootlevel));
|
||
$xpath = explode("/", $path);
|
||
|
||
// Set API type
|
||
if (strtolower($xpath[0]) == "image") {
|
||
$this->request["api"] = "image";
|
||
} elseif (count($xpath) > 1 || $xpath[0] != "") {
|
||
$this->request["api"] = "presentation";
|
||
} else {
|
||
$this->request["api"] = "root";
|
||
return;
|
||
}
|
||
|
||
if ($this->request["api"] == "image") {
|
||
// For image need to extract: -
|
||
// - Resource ID
|
||
// - type (manifest)
|
||
// - region
|
||
// - size
|
||
// - rotation
|
||
// - quality
|
||
// - format
|
||
$this->request["id"] = trim($xpath[1] ?? '');
|
||
$this->request["region"] = trim($xpath[2] ?? '');
|
||
$this->request["size"] = trim($xpath[3] ?? '');
|
||
$this->request["rotation"] = trim($xpath[4] ?? '');
|
||
$this->request["filename"] = trim($xpath[5] ?? '');
|
||
|
||
if ($this->request["id"] === '') {
|
||
$this->errors[] = 'Missing identifier';
|
||
$this->triggerError(400);
|
||
}
|
||
|
||
if ($this->request["region"] == "") {
|
||
// Redirect to image information document
|
||
$redirurl = $this->rootimageurl . $this->request["id"] . '/info.json';
|
||
if (function_exists("http_response_code")) {
|
||
http_response_code(303);
|
||
}
|
||
header("Location: " . $redirurl);
|
||
exit();
|
||
}
|
||
// Check the request parameters
|
||
elseif ($this->request["region"] != "info.json") {
|
||
if (
|
||
$this->request["size"] == ""
|
||
|| !is_int_loose($this->request["rotation"])
|
||
|| $this->request["filename"] != "default.jpg"
|
||
) {
|
||
// Not request for image information document and no sizes specified
|
||
$this->errors[] = "Invalid image request format.";
|
||
$this->triggerError(400);
|
||
}
|
||
|
||
$formatparts = explode(".", $this->request["filename"]);
|
||
if (count($formatparts) != 2) {
|
||
// Format. As we only support IIIF Image level 0 a value of 'jpg' is required
|
||
$this->errors[] = ["Invalid quality or format requested. Try using 'default.jpg'"];
|
||
$this->triggerError(400);
|
||
} else {
|
||
$this->request["quality"] = $formatparts[0];
|
||
$this->request["format"] = $formatparts[1];
|
||
}
|
||
}
|
||
} elseif ($this->request["api"] == "presentation") {
|
||
// Presentation - need
|
||
// - identifier
|
||
// - type (manifest/canvas/sequence/annotation
|
||
// - typeid (manifest/canvas/sequence/annotation
|
||
|
||
$this->request["id"] = trim($xpath[0] ?? '');
|
||
$this->request["type"] = trim($xpath[1] ?? '');
|
||
$this->request["typeid"] = trim($xpath[2] ?? '');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Find all the resources to generate an array of all the canvases for the identifier ready for JSON encoding
|
||
*
|
||
* @param boolean $sequencekeys Get the array with each key matching the value set in the metadata field $iiif_sequence_field. By default the array will be sorted but have a 0 based index
|
||
*
|
||
* @return void
|
||
*
|
||
*/
|
||
public function getCanvases($sequencekeys = false): void
|
||
{
|
||
$canvases = [];
|
||
foreach ($this->searchresults as $index => $iiif_result) {
|
||
if (in_array(strtolower($iiif_result["file_extension"] ?? ""), $this->media_extensions)) {
|
||
$size = "";
|
||
$media_path = get_resource_path($iiif_result["ref"], true, $size, false, $iiif_result["file_extension"]);
|
||
} else {
|
||
$size = $this->largest_jpg_size($iiif_result);
|
||
$media_path = get_resource_path($iiif_result["ref"], true, $size, false);
|
||
}
|
||
if (!file_exists($media_path)) {
|
||
// If configured, try and use a preview from a related resource
|
||
$pullresource = related_resource_pull($iiif_result);
|
||
if ($pullresource !== false) {
|
||
$this->processing["resource"] = $pullresource["ref"];
|
||
$this->processing["size_info"] = [
|
||
'identifier' => $this->largest_jpg_size($pullresource),
|
||
'return_height_width' => false,
|
||
];
|
||
}
|
||
}
|
||
$canvas = $this->generateCanvas($index);
|
||
if ($canvas) {
|
||
$canvases[$index] = $canvas;
|
||
}
|
||
}
|
||
|
||
if ($sequencekeys) {
|
||
// keep the sequence identifiers as keys so a required canvas can be accessed by sequence id
|
||
$this->response["items"] = $canvases;
|
||
}
|
||
ksort($canvases);
|
||
foreach ($canvases as $canvas) {
|
||
$this->response["items"][] = $canvas;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get thumbnail information for the specified resource id ready for IIIF JSON encoding
|
||
*
|
||
* @uses get_resource_path()
|
||
* @uses getimagesize()
|
||
*
|
||
* @param int $resourceid Resource ID
|
||
|
||
* @return array|bool Thumbnail image data, false if not found
|
||
*/
|
||
public function getThumbnail(int $resourceid)
|
||
{
|
||
$img_path = get_resource_path($resourceid, true, 'thm', false);
|
||
if (!file_exists($img_path)) {
|
||
return false;
|
||
}
|
||
|
||
$thumbnail = [];
|
||
$thumbnail["id"] = $this->rootimageurl . $resourceid . "/full/thm/0/default.jpg";
|
||
$thumbnail["type"] = "Image";
|
||
$thumbnail["format"] = "image/jpeg";
|
||
|
||
// Get the size of the images
|
||
$GLOBALS["use_error_exception"] = true;
|
||
try {
|
||
list($tw,$th) = getimagesize($img_path);
|
||
$thumbnail["height"] = (int) $th;
|
||
$thumbnail["width"] = (int) $tw;
|
||
} catch (Exception $e) {
|
||
$returned_error = $e->getMessage();
|
||
debug("getThumbnail: Unable to get image size for file: $img_path - $returned_error");
|
||
// Use defaults
|
||
$thumbnail["height"] = 150;
|
||
$thumbnail["width"] = 150;
|
||
}
|
||
unset($GLOBALS["use_error_exception"]);
|
||
|
||
$thumbnail["service"] = [$this->generateImageService($resourceid)];
|
||
return $thumbnail;
|
||
}
|
||
|
||
/**
|
||
* Get the media file for the specified identifier canvas and resource id
|
||
*
|
||
* @param integer $resourceid Resource ID
|
||
* @param array $size ResourceSpace size information. Required information: identifier and whether it
|
||
* is required to return the height & width (e.g annotations don't require this info).
|
||
* Please note the identifier - use 'hpr' if the original file is not a JPG file AND
|
||
* the extension is not in the $iiif_media_extensions arrays.
|
||
* Example:
|
||
* $size_info = array(
|
||
* 'identifier' => 'hpr',
|
||
* 'return_height_width' => true
|
||
* );
|
||
*
|
||
* @return bool|array Array holding image file data. Returns false if no image available.
|
||
*/
|
||
public function get_media(int $resource, array $size_info)
|
||
{
|
||
// Quick validation of the size_info param
|
||
if (empty($size_info) || (!isset($size_info['identifier']) && !isset($size_info['return_height_width']))) {
|
||
return false;
|
||
}
|
||
$size = $size_info['identifier'];
|
||
$return_height_width = $size_info['return_height_width'];
|
||
|
||
$resdata = get_resource_data($resource);
|
||
if (in_array($resdata["file_extension"], array_merge($this->media_extensions))) {
|
||
$media_path = get_resource_path($resource, true, $size, false, $resdata["file_extension"]);
|
||
} else {
|
||
$useextension = strtolower($resdata["file_extension"]) == "jpeg" ? $resdata["file_extension"] : "jpg";
|
||
$media_path = get_resource_path($resource, true, $size, false, $useextension);
|
||
}
|
||
|
||
if (!file_exists($media_path)) {
|
||
// If configured, try and use a preview from a related resource
|
||
$resdata = get_resource_data($resource);
|
||
$pullresource = related_resource_pull($resdata);
|
||
if ($pullresource !== false) {
|
||
$resource = $pullresource["ref"];
|
||
$media_path = get_resource_path($resource, true, $this->largest_jpg_size($pullresource), false);
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
$media = [];
|
||
if (in_array($resdata["file_extension"], array_merge($this->media_extensions))) {
|
||
$media["duration"] = get_video_duration($media_path); // Also works for audio
|
||
$accesskey = generate_temp_download_key($GLOBALS["userref"], $resource, "");
|
||
$url = $GLOBALS["baseurl"] . "/pages/download.php";
|
||
$params = [
|
||
"ref" => $resource,
|
||
"ext" => $resdata["file_extension"],
|
||
"noattach" => true,
|
||
"access_key" => $accesskey,
|
||
];
|
||
$media["id"] = generateURL($url, $params);
|
||
$media["type"] = in_array(
|
||
strtolower($resdata["file_extension"]),
|
||
array_merge($GLOBALS["ffmpeg_audio_extensions"], ["mp3"])
|
||
) ? "Sound" : "Video";
|
||
|
||
/** {@see include/mime_types.php} */
|
||
$found_types = get_mime_types_by_extension($resdata['file_extension']);
|
||
$media["format"] = $found_types === [] ? 'application/octet-stream' : reset($found_types);
|
||
|
||
$size = "";
|
||
$iiif_thumb = $this->getThumbnail($resource);
|
||
if ($iiif_thumb) {
|
||
$media["thumbnail"][] = $iiif_thumb;
|
||
}
|
||
} else {
|
||
$media["id"] = $this->rootimageurl . $resource . "/full/max/0/default.jpg";
|
||
$media["type"] = "Image";
|
||
$media["format"] = "image/jpeg";
|
||
$media["service"] = [$this->generateImageService($resource)];
|
||
}
|
||
if ($return_height_width) {
|
||
$media_size = get_original_imagesize($resource, $media_path, $resdata["file_extension"]);
|
||
$media["height"] = intval($media_size[2]);
|
||
$media["width"] = intval($media_size[1]);
|
||
}
|
||
return $media;
|
||
}
|
||
|
||
/**
|
||
* Handle a IIIF error.
|
||
*
|
||
* @param integer $errorcode The error code
|
||
*
|
||
* @return void
|
||
*/
|
||
public function triggerError($errorcode = 404)
|
||
{
|
||
if (function_exists("http_response_code")) {
|
||
http_response_code($errorcode); # Send error status
|
||
}
|
||
echo json_encode($this->errors);
|
||
exit();
|
||
}
|
||
|
||
/**
|
||
* Process a IIIF presentation request
|
||
* @param object $iiif The current IIIF request object generated in api/iiif/handler.php
|
||
*
|
||
* @return void
|
||
*
|
||
*/
|
||
public function processPresentationRequest(): void
|
||
{
|
||
$this->getResources();
|
||
|
||
if (is_array($this->searchresults) && count($this->searchresults) > 0) {
|
||
if ($this->request["type"] == "manifest" || $this->request["type"] == "") {
|
||
$this->generateManifest();
|
||
$this->validrequest = true;
|
||
} elseif ($this->request["type"] == "canvas") {
|
||
$this->getResourceFromPosition($this->request["typeid"]);
|
||
|
||
$this->response = $this->generateCanvas($this->request["typeid"]);
|
||
$this->validrequest = true;
|
||
} elseif ($this->request["type"] == "annotationpage") {
|
||
$this->getResourceFromPosition($this->request["typeid"]);
|
||
$this->response = $this->generateAnnotationPage($this->request["typeid"]);
|
||
$this->validrequest = true;
|
||
} elseif ($this->request["type"] == "annotation") {
|
||
$this->getResourceFromPosition($this->request["typeid"]);
|
||
$this->response = $this->generateAnnotation($this->request["typeid"]);
|
||
$this->validrequest = true;
|
||
}
|
||
} // End of valid $identifier check based on search results
|
||
else {
|
||
$this->errorcode = 404;
|
||
$this->errors[] = "Invalid identifier: " . $this->request["id"];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Generate the top level manifest - see http://iiif.io/api/presentation/3.0/#manifest
|
||
*
|
||
* @return void
|
||
*/
|
||
public function generateManifest(): void
|
||
{
|
||
global $lang, $defaultlanguage;
|
||
$this->response["@context"] = "http://iiif.io/api/presentation/3/context.json";
|
||
$this->response["id"] = $this->rooturl . $this->request["id"] . "/manifest";
|
||
$this->response["type"] = "Manifest";
|
||
|
||
// Descriptive metadata about the object/work
|
||
// The manifest data should be the same for all resources that are returned.
|
||
// This is the default when using the tms_link plugin for TMS integration.
|
||
// Therefore we use the data from the first returned result.
|
||
$dataresource = reset($this->searchresults);
|
||
$this->data = get_resource_field_data($dataresource["ref"]);
|
||
|
||
// Label property
|
||
foreach ($this->searchresults as $iiif_result) {
|
||
// Keep on until we find a label
|
||
$iiif_label = get_data_by_field($iiif_result["ref"], $this->title_field);
|
||
if (trim($iiif_label) != "") {
|
||
$i18n_values = i18n_get_translations($iiif_label);
|
||
foreach ($i18n_values as $langcode => $langstring) {
|
||
$this->response["label"][$langcode] = [$langstring];
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
if (!$iiif_label) {
|
||
$this->response["label"][$defaultlanguage] = [$lang["notavailableshort"]];
|
||
}
|
||
|
||
foreach ($this->searchresults as $iiif_result) {
|
||
$description = get_data_by_field($iiif_result["ref"], $this->description_field);
|
||
if (trim($description) != "") {
|
||
$i18n_values = i18n_get_translations($description);
|
||
foreach ($i18n_values as $langcode => $langstring) {
|
||
$this->response["summary"][$langcode] = [$langstring];
|
||
}
|
||
break; // Only metadata from one resource is required
|
||
}
|
||
}
|
||
// Construct metadata array from resource field data
|
||
$this->generateMetadata();
|
||
if ($this->license_field != 0) {
|
||
$licensevals = get_data_by_field($dataresource["ref"], $this->license_field, false);
|
||
if (count($licensevals) > 0) {
|
||
// Get all field title translations
|
||
$licensefield = get_resource_type_field($this->license_field);
|
||
$liclabel_int = i18n_get_translations($licensefield["title"]);
|
||
$reqstatements = ["label" => [],"value" => []];
|
||
foreach ($licensevals as $licenseval) {
|
||
$licensevals_int = i18n_get_translations($licenseval["name"]);
|
||
foreach ($licensevals_int as $langcode => $langstring) {
|
||
if (!isset($reqstatements["label"][$langcode])) {
|
||
// Translated node names may include languages that are not available for the field title
|
||
$reqstatements["label"][$langcode][] = $liclabel_int[$langcode] ?? $licensefield["title"];
|
||
}
|
||
$reqstatements["value"][$langcode][] = $langstring;
|
||
}
|
||
}
|
||
|
||
$this->response["requiredStatement"] = $reqstatements;
|
||
}
|
||
}
|
||
if (isset($this->rights_statement) && $this->rights_statement != "") {
|
||
$this->response["rights_statement"] = $this->rights_statement;
|
||
}
|
||
|
||
// Thumbnail property
|
||
$this->response["thumbnail"] = [];
|
||
foreach ($this->searchresults as $iiif_result) {
|
||
// Keep on until we find an image
|
||
$iiif_thumb = $this->getThumbnail($dataresource["ref"]);
|
||
if ($iiif_thumb) {
|
||
$this->response["thumbnail"][] = $iiif_thumb;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Default behavior property - not currently configurable
|
||
$this->response["behavior"] = ["individuals"];
|
||
|
||
// Default viewingDirection property - not currently configurable
|
||
$this->response["viewingDirection"] = "left-to-right";
|
||
|
||
$this->getCanvases(false);
|
||
}
|
||
|
||
/**
|
||
* Generate a canvas
|
||
*
|
||
* @param int $position The canvas identifier
|
||
*
|
||
* @return array|bool $canvas Canvas data for presentation API response, false if no image is available
|
||
*
|
||
*/
|
||
public function generateCanvas(int $position)
|
||
{
|
||
// This is essentially a resource
|
||
// {scheme}://{host}/{prefix}/{identifier}/canvas/{name}
|
||
$canvas = [];
|
||
$resource = $this->searchresults[$position] ?? [];
|
||
if (empty($resource)) {
|
||
debug("IIIF: generateCanvas() Not a valid canvas identifier:" . $position);
|
||
return false;
|
||
}
|
||
$useimage = $resource;
|
||
if ((int)$resource['has_image'] === 0) {
|
||
// If configured, try and use a preview from a related resource
|
||
debug("No image for IIIF request - check for related resources");
|
||
$pullresource = related_resource_pull($resource);
|
||
if ($pullresource !== false) {
|
||
$useimage = $pullresource;
|
||
}
|
||
}
|
||
|
||
if (in_array(strtolower($useimage['file_extension'] ?? ""), $this->media_extensions)) {
|
||
$size = '';
|
||
$media_path = get_resource_path($useimage["ref"], true, $size, false, $useimage["file_extension"]);
|
||
} else {
|
||
$size = $this->largest_jpg_size($useimage);
|
||
$useextension = strtolower((string) $useimage["file_extension"]) == "jpeg" ? $useimage["file_extension"] : "jpg";
|
||
$media_path = get_resource_path($useimage["ref"], true, $size, false, $useextension);
|
||
}
|
||
if (!file_exists($media_path)) {
|
||
debug("IIIF: generateCanvas() No image available for identifier:" . $position);
|
||
return false;
|
||
}
|
||
$sequence_field = get_resource_type_field($this->sequence_field);
|
||
$sequenceid = $resource["iiif_position"];
|
||
debug("IIIF: Found resource " . $resource['ref'] . " in position " . $position . ", sequence ID: " . $sequenceid);
|
||
$sequence_prefix = "";
|
||
if (isset($this->iiif_sequence_prefix)) {
|
||
$sequence_prefix = $this->iiif_sequence_prefix === "" ? $sequence_field["title"] . " " : $this->iiif_sequence_prefix;
|
||
}
|
||
$sequence_val = $sequenceid;
|
||
$canvas["id"] = $this->rooturl . $this->request["id"] . "/canvas/" . $position;
|
||
$canvas["type"] = "Canvas";
|
||
$canvas["label"] = [];
|
||
$arr_18n_pos_labels = i18n_get_translations($sequence_val);
|
||
$arr_18n_pos_prefixes = i18n_get_translations($sequence_prefix);
|
||
if (count($arr_18n_pos_prefixes) > 1 || count($arr_18n_pos_labels) > 1) {
|
||
foreach (array_unique(array_merge(array_keys($arr_18n_pos_prefixes), array_keys($arr_18n_pos_labels))) as $langcode) {
|
||
$prefix = $arr_18n_pos_prefixes[$langcode] ?? ($arr_18n_pos_prefixes[$GLOBALS["defaultlanguage"]] ?? reset($arr_18n_pos_prefixes));
|
||
$labelvalue = $arr_18n_pos_labels[$langcode] ?? ($arr_18n_pos_labels[$GLOBALS["defaultlanguage"]] ?? reset($arr_18n_pos_labels));
|
||
$canvas["label"][$langcode] = [$prefix . $labelvalue];
|
||
}
|
||
} else {
|
||
$canvas["label"]["none"] = [$sequence_prefix . $sequence_val];
|
||
}
|
||
|
||
// Get the size of the images
|
||
$image_size = get_original_imagesize($useimage["ref"], $media_path, $useimage["file_extension"]);
|
||
$canvas["height"] = intval($image_size[2]);
|
||
$canvas["width"] = intval($image_size[1]);
|
||
|
||
// Add image (only 1 per canvas currently supported)
|
||
$this->getResourceFromPosition($position);
|
||
$canvas["items"][] = $this->generateAnnotationPage($position);
|
||
|
||
return $canvas;
|
||
}
|
||
|
||
/**
|
||
* Generate the AnnotationPage elements
|
||
*
|
||
* @param int $position The annotation position
|
||
*
|
||
* @return array Array of annotation pages
|
||
*
|
||
*/
|
||
public function generateAnnotationPage(int $position = 0): array
|
||
{
|
||
$annotationpage = [];
|
||
$annotationpage["id"] = $this->rooturl . $this->request["id"] . "/annotationpage/" . $position;
|
||
$annotationpage["type"] = "AnnotationPage";
|
||
$annotationpage["items"] = [];
|
||
$annotationpage["items"][] = $this->generateAnnotation($position);
|
||
return $annotationpage;
|
||
}
|
||
|
||
/**
|
||
* Generate the Annotation elements
|
||
*
|
||
* @return array Array of annotations
|
||
*/
|
||
public function generateAnnotation(int $position = 0): array
|
||
{
|
||
$annotation["id"] = $this->rooturl . $this->request["id"] . "/annotation/" . $position;
|
||
$annotation["type"] = "Annotation";
|
||
$annotation["motivation"] = "Painting";
|
||
$annotation["body"] = $this->get_media($this->processing["resource"], $this->processing["size_info"]);
|
||
$annotation["target"] = $this->rooturl . $this->request["id"] . "/canvas/" . $position;
|
||
return $annotation;
|
||
}
|
||
|
||
/**
|
||
* Generates the IIIF response for the current IIIF object (presentation API)
|
||
*
|
||
*
|
||
* @return void
|
||
*/
|
||
public function generateMetadata(): void
|
||
{
|
||
$metadata = [];
|
||
$n = 0;
|
||
foreach ($this->data as $iiif_data_row) {
|
||
if (in_array($iiif_data_row["type"], $GLOBALS["FIXED_LIST_FIELD_TYPES"])) {
|
||
// Don't use the data as this has already concatenated the translations, add an entry for each node translation by building up a new array
|
||
$resnodes = get_resource_nodes(reset($this->searchresults)["ref"], $iiif_data_row["resource_type_field"], true);
|
||
if (count($resnodes) == 0) {
|
||
continue;
|
||
}
|
||
// Add all translated field names
|
||
$metadata[$n] = [];
|
||
$metadata[$n]["label"] = [];
|
||
$i18n_titles = i18n_get_translations($iiif_data_row["title"]);
|
||
foreach ($i18n_titles as $langcode => $langstring) {
|
||
$metadata[$n]["label"][$langcode] = [$langstring];
|
||
}
|
||
|
||
// Add all translated node names
|
||
$arr_showlangs = [];
|
||
$arr_alllangstrings = [];
|
||
$arr_lang_default = [];
|
||
foreach ($resnodes as $resnode) {
|
||
$node_langs_avail = [];
|
||
$i18n_names = i18n_get_translations($resnode["name"]);
|
||
// Set default in case no translation available for any languages
|
||
$defaultnodename = $i18n_names[$GLOBALS["defaultlanguage"]] ?? reset($i18n_names);
|
||
$arr_lang_default[] = $defaultnodename;
|
||
foreach ($i18n_names as $langcode => $langstring) {
|
||
$node_langs_avail[] = $langcode;
|
||
if (!isset($arr_alllangstrings[$langcode])) {
|
||
// This is the first time this language has been found for this field
|
||
// Initialise the language by copying the default array of values found so far
|
||
$arr_alllangstrings[$langcode] = $arr_lang_default;
|
||
}
|
||
// Add to array
|
||
$arr_alllangs[$langcode][] = $langstring;
|
||
$arr_showlangs[] = $langcode;
|
||
}
|
||
|
||
// Check that this node string has been added for all translations found so far
|
||
foreach ($arr_alllangstrings as $langcode => $strings) {
|
||
if (!in_array($langcode, $node_langs_avail)) {
|
||
$arr_alllangstrings[$langcode][] = $defaultnodename;
|
||
}
|
||
}
|
||
}
|
||
$metadata[$n]["value"] = [];
|
||
foreach ($arr_alllangstrings as $langcode => $strings) {
|
||
$metadata[$n]["value"][$langcode] = [implode(NODE_NAME_STRING_SEPARATOR, $strings)];
|
||
}
|
||
} elseif (trim((string) $iiif_data_row["value"]) !== "") {
|
||
$metadata[$n] = [];
|
||
$metadata[$n]["label"] = [];
|
||
$i18n_titles = i18n_get_translations($iiif_data_row["title"]);
|
||
foreach ($i18n_titles as $langcode => $langstring) {
|
||
$metadata[$n]["label"][$langcode] = [$langstring];
|
||
}
|
||
$metadata[$n]["value"] = [];
|
||
$i18n_titles = i18n_get_translations($iiif_data_row["value"]);
|
||
foreach ($i18n_titles as $langcode => $langstring) {
|
||
$metadata[$n]["value"][$langcode] = [$langstring];
|
||
}
|
||
$n++;
|
||
}
|
||
}
|
||
$this->response["metadata"] = $metadata;
|
||
}
|
||
|
||
/**
|
||
* Process the IIIF Image API request - see http://iiif.io/api/image/3.0/
|
||
* The IIIF Image API URI for requesting an image must conform to the following URI Template:
|
||
* {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format}
|
||
*
|
||
* @return void
|
||
*
|
||
*/
|
||
public function processImageRequest(): void
|
||
{
|
||
$this->request["getext"] = "jpg";
|
||
if ($this->request["id"] === '') {
|
||
$this->errors[] = 'Missing identifier';
|
||
$this->triggerError(400);
|
||
}
|
||
|
||
if ($this->request["region"] == "") {
|
||
// Redirect to image information document
|
||
$redirurl = $this->rootimageurl . $this->request["id"] . '/info.json';
|
||
if (function_exists("http_response_code")) {
|
||
http_response_code(303);
|
||
}
|
||
header("Location: " . $redirurl);
|
||
exit();
|
||
}
|
||
|
||
if (is_numeric($this->request["id"])) {
|
||
$resource = get_resource_data($this->request["id"]);
|
||
$resource_access = get_resource_access($this->request["id"]);
|
||
} else {
|
||
$resource_access = 2;
|
||
}
|
||
if (
|
||
$resource_access == 0
|
||
&& !in_array($resource["file_extension"], array_diff(config_merge_non_image_types(), $this->media_extensions))
|
||
) {
|
||
// Check resource actually exists and is active
|
||
if (in_array($resource["file_extension"], $this->media_extensions)) {
|
||
$fulljpgsize = "pre";
|
||
} else {
|
||
$fulljpgsize = $this->largest_jpg_size($resource);
|
||
}
|
||
$useextension = strtolower($resource["file_extension"]) == "jpeg" ? $resource["file_extension"] : "jpg";
|
||
$img_path = get_resource_path($this->request["id"], true, $fulljpgsize, false, $useextension);
|
||
$image_size = get_original_imagesize($this->request["id"], $img_path, $useextension);
|
||
if ($image_size === false) {
|
||
$this->errors[] = "No image available for this identifier";
|
||
$this->triggerError(404);
|
||
}
|
||
$this->imagewidth = (int) $image_size[1];
|
||
$this->imageheight = (int) $image_size[2];
|
||
|
||
// Get all available sizes
|
||
$sizes = get_image_sizes($this->request["id"], true, "jpg", false);
|
||
$availsizes = [];
|
||
if ($this->imagewidth > 0 && $this->imageheight > 0) {
|
||
foreach ($sizes as $size) {
|
||
if (
|
||
$size['width'] > 0
|
||
&& $size['height'] > 0
|
||
&& $size['width'] <= $this->max_width
|
||
&& $size['height'] <= $this->max_height
|
||
&& (
|
||
!$this->only_power_of_two_sizes
|
||
|| (is_power_of_two($size['width']) && is_power_of_two($size['height']))
|
||
|| $size['id'] == 'pre'
|
||
)
|
||
) {
|
||
$availsizes[] = [
|
||
'id' => $size['id'],
|
||
'width' => $size['width'],
|
||
'height' => $size['height'],
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($this->request["region"] == "info.json") {
|
||
// Image information request. Only fullsize available in this initial version
|
||
$this->response["@context"] = "http://iiif.io/api/image/3/context.json";
|
||
$this->response["extraFormats"] = [
|
||
"jpg",
|
||
];
|
||
$this->response["extraQualities"] = [
|
||
"default",
|
||
];
|
||
$this->response["id"] = $this->rootimageurl . $this->request["id"];
|
||
|
||
$this->response["height"] = $this->imageheight;
|
||
$this->response["width"] = $this->imagewidth;
|
||
|
||
$this->response["type"] = "ImageService3";
|
||
$this->response["profile"] = "level0";
|
||
$this->response["maxWidth"] = $this->max_width;
|
||
$this->response["maxHeight"] = $this->max_height;
|
||
if ($this->custom_sizes) {
|
||
$this->response["extraFeatures"] = ["sizeByH","sizeByW","sizeByWh"];
|
||
}
|
||
|
||
$this->response["protocol"] = "http://iiif.io/api/image";
|
||
$this->response["sizes"] = $availsizes;
|
||
if ($this->preview_tiles) {
|
||
$this->response["tiles"] = [];
|
||
$this->response["tiles"][] = array("height" => $this->preview_tile_size, "width" => $this->preview_tile_size, "scaleFactors" => $this->preview_tile_scale_factors);
|
||
}
|
||
$this->headers[] = 'Link: <http://iiif.io/api/image/3/level0.json>;rel="profile"';
|
||
$this->validrequest = true;
|
||
} else {
|
||
// Process requested region
|
||
if (!isset($this->errorcode) && $this->request["region"] != "full" && $this->request["region"] != "max" && $this->preview_tiles) {
|
||
// If the request specifies a region which extends beyond the dimensions reported in the image information document,
|
||
// then the service should return an image cropped at the image’s edge, rather than adding empty space.
|
||
// If the requested region’s height or width is zero, or if the region is entirely outside the bounds
|
||
// of the reported dimensions, then the server should return a 400 status code.
|
||
|
||
$regioninfo = explode(",", $this->request["region"]);
|
||
$region_filtered = array_filter($regioninfo, 'is_numeric');
|
||
if (count($region_filtered) != 4) {
|
||
// Invalid region
|
||
$this->errors[] = "Invalid region requested. Use 'full' or 'x,y,w,h'";
|
||
$this->triggerError(400);
|
||
} else {
|
||
$this->regionx = (int)$region_filtered[0];
|
||
$this->regiony = (int)$region_filtered[1];
|
||
$this->regionw = (int)$region_filtered[2];
|
||
$this->regionh = (int)$region_filtered[3];
|
||
debug("IIIF: region requested: x:" . $this->regionx . ", y:" . $this->regiony . ", w:" . $this->regionw . ", h:" . $this->regionh);
|
||
if (fmod($this->regionx, $this->preview_tile_size) != 0 || fmod($this->regiony, $this->preview_tile_size) != 0) {
|
||
// Invalid region
|
||
$this->errors[] = "Invalid region requested. Supported tiles are " . $this->preview_tile_size . "x" . $this->preview_tile_size . " at scale factors " . implode(",", $this->preview_tile_scale_factors) . ".";
|
||
$this->triggerError(400);
|
||
} else {
|
||
$tile_request = true;
|
||
}
|
||
}
|
||
} else {
|
||
// Full image requested
|
||
$tile_request = false;
|
||
}
|
||
|
||
// Process size
|
||
if (strpos($this->request["size"], ",") !== false) {
|
||
// Currently support 'w,' and ',h' syntax requests
|
||
$getdims = explode(",", $this->request["size"]);
|
||
$this->getwidth = (int)$getdims[0];
|
||
$this->getheight = (int)$getdims[1];
|
||
if ($tile_request) {
|
||
if (!$this->isValidTileRequest()) {
|
||
$this->errors[] = "Invalid tile size requested";
|
||
$this->triggerError(400);
|
||
}
|
||
if ($this->getheight === 0) {
|
||
$scale = ceil($this->regionw / $this->getwidth);
|
||
} else {
|
||
$scale = ceil($this->regionh / $this->getheight);
|
||
}
|
||
$this->request["getsize"] = "tile_" . $scale . "_" . $this->regionx . "_" . $this->regiony . "_" . $this->regionw . "_" . $this->regionh;
|
||
debug("IIIF: " . $this->regionx . "_" . $this->regiony . "_" . $this->regionw . "_" . $this->regionh);
|
||
} else {
|
||
if ($this->getheight == 0) {
|
||
$this->getheight = floor($this->getwidth * ($this->imageheight / $this->imagewidth));
|
||
} elseif ($this->getwidth == 0) {
|
||
$this->getwidth = floor($this->getheight * ($this->imagewidth / $this->imageheight));
|
||
}
|
||
// Establish which preview size this request relates to
|
||
foreach ($availsizes as $availsize) {
|
||
debug("IIIF: checking available size for resource " . $resource["ref"] . ". Size '" . $availsize["id"] . "': " . $availsize["width"] . "x" . $availsize["height"] . ". Requested size: " . $this->getwidth . "x" . $this->getheight);
|
||
if ($availsize["width"] == $this->getwidth && $availsize["height"] == $this->getheight) {
|
||
$this->request["getsize"] = $availsize["id"];
|
||
}
|
||
}
|
||
if (!isset($this->request["getsize"])) {
|
||
if (!$this->custom_sizes || $this->getwidth > $this->max_width || $this->getheight > $this->max_height) {
|
||
// Invalid size requested
|
||
$this->errors[] = "Invalid size requested";
|
||
$this->triggerError(400);
|
||
} else {
|
||
$this->request["getsize"] = "resized_" . $this->getwidth . "_" . $this->getheight;
|
||
}
|
||
}
|
||
}
|
||
} elseif ($this->request["size"] == "full" || $this->request["size"] == "max" || $this->request["size"] == "thm") {
|
||
if ($tile_request) {
|
||
if ($this->request["size"] == "full" || $this->request["size"] == "max") {
|
||
$scale = ceil($this->regionw / $this->preview_tile_size);
|
||
$this->request["getsize"] = "tile_" . $scale . "_" . $this->regionx . "_" . $this->regiony . "_" . $this->regionw . "_" . $this->regionh;
|
||
$this->request["getext"] = "jpg";
|
||
} else {
|
||
$this->errors[] = "Invalid tile size requested";
|
||
$this->triggerError(501);
|
||
}
|
||
} else {
|
||
// Full/max image region requested
|
||
if ($this->max_width >= $this->imagewidth && $this->max_height >= $this->imageheight) {
|
||
$this->request["getext"] = strtolower($resource["file_extension"]) == "jpeg" ? "jpeg" : "jpg";
|
||
if (in_array($resource["file_extension"], $this->media_extensions)) {
|
||
// The largest available size for these is 'pre'
|
||
$this->request["getsize"] = "pre";
|
||
} else {
|
||
$this->request["getsize"] = $this->largest_jpg_size($resource);
|
||
}
|
||
} else {
|
||
$this->request["getext"] = "jpg";
|
||
$this->request["getsize"] = count($availsizes) > 0 ? $availsizes[0]["id"] : "thm";
|
||
}
|
||
}
|
||
} else {
|
||
$this->errors[] = "Invalid size requested";
|
||
$this->triggerError(400);
|
||
}
|
||
|
||
if ($this->request["rotation"] != 0) {
|
||
// Rotation. As we only support IIIF Image level 0 only a rotation value of 0 is accepted
|
||
$this->errors[] = "Invalid rotation requested. Only '0' is permitted.";
|
||
$this->triggerError(404);
|
||
}
|
||
if (isset($this->request["quality"]) && $this->request["quality"] != "default" && $this->request["quality"] != "color") {
|
||
// Quality. As we only support IIIF Image level 0 only a quality value of 'default' or 'color' is accepted
|
||
$this->errors[] = "Invalid quality requested. Only 'default' is permitted";
|
||
$this->triggerError(404);
|
||
}
|
||
if (isset($this->request["format"]) && strtolower($this->request["format"]) != "jpg") {
|
||
// Format. As we only support IIIF Image level 0 only a value of 'jpg' is accepted
|
||
$this->errors[] = "Invalid format requested. Only 'jpg' is permitted.";
|
||
$this->triggerError(404);
|
||
}
|
||
|
||
if (!isset($this->errorcode)) {
|
||
// Request is supported, send the image
|
||
$imgpath = get_resource_path($this->request["id"], true, $this->request["getsize"], false, $this->request["getext"]);
|
||
if ($tile_request && !file_exists($imgpath)) {
|
||
// Support older tiles without scale factor in ID that may not have been recreated
|
||
$imgpath = preg_replace("/(tile_\\d+_)/", "tile_", $imgpath);
|
||
}
|
||
$imgfound = false;
|
||
debug("IIIF: image path: " . $imgpath);
|
||
if (file_exists($imgpath)) {
|
||
$imgfound = true;
|
||
} elseif ($this->custom_sizes && ($this->request["region"] == "full" || $this->request["region"] == "max")) {
|
||
if (is_process_lock('create_previews_' . $resource["ref"] . "_" . $this->request["getsize"])) {
|
||
$this->errors[] = "Requested image is not currently available";
|
||
$this->triggerError(503);
|
||
}
|
||
$GLOBALS["use_error_exception"] = true;
|
||
try {
|
||
$imgfound = create_previews($this->request["id"], false, "jpg", false, true, -1, true, false, false, array($this->request["getsize"]));
|
||
clear_process_lock('create_previews_' . $resource["ref"] . "_" . $this->request["getsize"]);
|
||
} catch (Exception $e) {
|
||
debug("IIIF: error - " . $e->getMessage());
|
||
$imgfound = false;
|
||
}
|
||
unset($GLOBALS["use_error_exception"]);
|
||
}
|
||
if ($imgfound) {
|
||
$this->validrequest = true;
|
||
$this->response["image"] = $imgpath;
|
||
} else {
|
||
$this->errorcode = "404";
|
||
$this->errors[] = "No image available for this identifier";
|
||
}
|
||
}
|
||
}
|
||
/* IMAGE REQUEST END */
|
||
} else {
|
||
$this->errors[] = "Missing or invalid identifier";
|
||
$this->triggerError(404);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Send the requested image to the IIIF client
|
||
*
|
||
* @return void
|
||
*/
|
||
public function renderImage(): void
|
||
{
|
||
// Send the image
|
||
$file_size = filesize_unlimited($this->response["image"]);
|
||
$file_handle = fopen($this->response["image"], 'rb');
|
||
header("Access-Control-Allow-Origin: *");
|
||
header('Content-Disposition: inline;');
|
||
header('Content-Transfer-Encoding: binary');
|
||
$mime = get_mime_type($this->response["image"])[0];
|
||
header("Content-Type: {$mime}");
|
||
$sent = 0;
|
||
while ($sent < $file_size) {
|
||
echo fread($file_handle, $this->download_chunk_size);
|
||
ob_flush();
|
||
flush();
|
||
$sent += $this->download_chunk_size;
|
||
if (0 != connection_status()) {
|
||
break;
|
||
}
|
||
}
|
||
fclose($file_handle);
|
||
}
|
||
|
||
/**
|
||
* Find all resources associated with the given identifier and adds to the $iiif object
|
||
*
|
||
* @return void
|
||
*
|
||
*/
|
||
public function getResources(): void
|
||
{
|
||
$iiif_field = get_resource_type_field($this->identifier_field);
|
||
$iiif_search = $iiif_field["name"] . ":" . $this->request["id"];
|
||
$results = do_search($iiif_search);
|
||
if (is_array($results)) {
|
||
$this->searchresults = $results;
|
||
} else {
|
||
$this->searchresults = [];
|
||
}
|
||
|
||
// Add sequence position information
|
||
$resultcount = count($this->searchresults);
|
||
$iiif_results_with_position = [];
|
||
$iiif_results_without_position = [];
|
||
for ($n = 0; $n < $resultcount; $n++) {
|
||
if ($this->sequence_field != 0) {
|
||
if (isset($this->searchresults[$n]["field" . $this->sequence_field])) {
|
||
$sequenceid = $this->searchresults[$n]["field" . $this->sequence_field];
|
||
} else {
|
||
$sequenceid = get_data_by_field($this->searchresults[$n]["ref"], $this->sequence_field);
|
||
}
|
||
|
||
if (!isset($sequenceid) || trim($sequenceid) == "") {
|
||
// Processing resources without a sequence position separately
|
||
debug("IIIF: position empty for resource ref " . $this->searchresults[$n]["ref"]);
|
||
$iiif_results_without_position[] = $this->searchresults[$n];
|
||
continue;
|
||
}
|
||
|
||
debug("IIIF: position $sequenceid found in resource ref " . $this->searchresults[$n]["ref"]);
|
||
$this->searchresults[$n]["iiif_position"] = $sequenceid;
|
||
$iiif_results_with_position[] = $this->searchresults[$n];
|
||
} else {
|
||
$sequenceid = $n;
|
||
debug("IIIF: position $sequenceid assigned to resource ref " . $this->searchresults[$n]["ref"]);
|
||
$this->searchresults[$n]["iiif_position"] = $sequenceid;
|
||
$iiif_results_with_position[] = $this->searchresults[$n];
|
||
}
|
||
}
|
||
|
||
// Sort by user supplied position (handle blanks and duplicates)
|
||
if ($this->sequence_field != 0) {
|
||
# First sort by ref. Any duplicate positions will then be sorted oldest resource first.
|
||
usort($iiif_results_with_position, function ($a, $b) {
|
||
return $a['ref'] - $b['ref'];
|
||
});
|
||
# Sort resources with user supplied position.
|
||
usort($iiif_results_with_position, function ($a, $b) {
|
||
if (is_int_loose($a['iiif_position']) && is_int_loose($b['iiif_position'])) {
|
||
return $a['iiif_position'] - $b['iiif_position'];
|
||
} elseif (is_int_loose($a['iiif_position']) || is_int_loose($b['iiif_position'])) {
|
||
return is_int_loose($a['iiif_position']) ? 1 : -1; // Put strings before numbers
|
||
}
|
||
return strcmp($a['iiif_position'], $b['iiif_position']);
|
||
});
|
||
|
||
if (count($iiif_results_without_position) > 0 && count($iiif_results_with_position) > 0) {
|
||
# Sort resources without a user supplied position by resource reference.
|
||
# These will appear at the end of the sequence after those with a user supplied position.
|
||
# Only applies if some resources have a sequence position else return in search results order per earlier behaviour.
|
||
usort($iiif_results_without_position, function ($a, $b) {
|
||
return $a['ref'] - $b['ref'];
|
||
});
|
||
}
|
||
|
||
$this->searchresults = array_merge($iiif_results_with_position, $iiif_results_without_position);
|
||
$sorted_final = [];
|
||
$maxid = 0;
|
||
foreach ($this->searchresults as $index => $resource) {
|
||
# Update iiif_position after sorting using unique array key, removing potential user entered duplicates in sequence field.
|
||
# iiif_get_canvases() requires unique iiif_position values.
|
||
$resourcepos = $resource['iiif_position'] ?? ($maxid + 1);
|
||
while (isset($sorted_final[$resourcepos])) {
|
||
$resourcepos++;
|
||
}
|
||
|
||
debug("IIIF: final position $index given for resource ref " . $resource["ref"] . " sequence id: " . $resourcepos);
|
||
$sorted_final[$index] = $resource;
|
||
$sorted_final[$index]["iiif_position"] = $resourcepos;
|
||
$maxid = max((int) $resourcepos, $maxid);
|
||
}
|
||
|
||
$this->searchresults = $sorted_final;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update the $iiif object with the current resource at the given canvas position
|
||
*
|
||
* @param int $position The annotation position
|
||
*
|
||
* @return void
|
||
*
|
||
*/
|
||
public function getResourceFromPosition($position): void
|
||
{
|
||
$this->processing = [];
|
||
// Need to find the resourceid the annotation is linked to
|
||
if (isset($this->searchresults[$position])) {
|
||
$this->processing["resource"] = $this->searchresults[$position]["ref"];
|
||
if (in_array(strtolower($this->searchresults[$position]['file_extension'] ?? ""), $this->media_extensions)) {
|
||
$identifier = '';
|
||
} else {
|
||
$identifier = $this->largest_jpg_size($this->searchresults[$position]);
|
||
}
|
||
$this->processing["size_info"] = array(
|
||
'identifier' => $identifier,
|
||
'return_height_width' => true,
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Generate the image API data
|
||
*
|
||
* @param int $resourceid Resource ID
|
||
*
|
||
* @return array
|
||
*
|
||
*/
|
||
public function generateImageService(int $resourceid): array
|
||
{
|
||
$service = [];
|
||
$service["id"] = $this->rootimageurl . $resourceid;
|
||
$service["type"] = "ImageService3";
|
||
$service["profile"] = "level0";
|
||
return $service;
|
||
}
|
||
|
||
/**
|
||
* Is the tile request valid
|
||
*
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function isValidTileRequest(): bool
|
||
{
|
||
if (
|
||
($this->getwidth == $this->preview_tile_size && $this->getheight == 0) // "w,"
|
||
|| ($this->getheight == $this->preview_tile_size && $this->getwidth == 0) // ",h"
|
||
|| ($this->getheight == $this->preview_tile_size && $this->getwidth == $this->preview_tile_size) // "w,h"
|
||
) {
|
||
// Standard tile widths
|
||
return true;
|
||
} elseif (
|
||
($this->regionx + $this->regionw) === ($this->imagewidth)
|
||
|| ((int)$this->regiony + (int)$this->regionh) === ((int)$this->imageheight)
|
||
) {
|
||
// Check this is a valid scale from the width/height requested.
|
||
// If using just e.g. "x," or ",y" then default to 1)
|
||
$hscale = $this->getwidth > 0 ? ceil($this->regionw / $this->getwidth) : 1;
|
||
$vscale = $this->getheight > 0 ? ceil($this->regionh / $this->getheight) : 1;
|
||
if (
|
||
($this->getwidth === 0 || $this->getheight === 0 || $hscale == $vscale)
|
||
&& count(array_diff([$hscale,$vscale], $this->preview_tile_scale_factors)) == 0
|
||
) {
|
||
return true;
|
||
}
|
||
}
|
||
debug('IIIF invalid tile request');
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Indicate whether the response is an image file
|
||
*
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function is_image_response()
|
||
{
|
||
return isset($this->response["image"]);
|
||
}
|
||
|
||
/**
|
||
* Get the largest resource JPG size available for a given resource in search result set
|
||
*
|
||
* @param array $resource Array of resource data from do_search()
|
||
*
|
||
* @return string Size to use - 'hpr', or '' to use original size
|
||
*
|
||
*/
|
||
public function largest_jpg_size($resource)
|
||
{
|
||
return is_jpeg_extension($resource["file_extension"] ?? "") ? "" : "hpr";
|
||
}
|
||
}
|
||
|
||
// Start of IIIF v2.1 functions. These should be replaced with new code or removed when no longer required
|
||
|
||
/**
|
||
* Get an array of all the canvases for the identifier ready for JSON encoding
|
||
*
|
||
* @uses get_data_by_field()
|
||
* @uses get_original_imagesize()
|
||
* @uses get_resource_type_field()
|
||
* @uses get_resource_path()
|
||
* @uses iiif_get_thumbnail()
|
||
* @uses iiif_get_image()
|
||
*
|
||
* @param integer $identifier IIIF identifier (this associates resources via the metadata field set as $iiif_identifier_field
|
||
* @param array $iiif_results Array of ResourceSpace search results that match the $identifier, sorted
|
||
* @param boolean $sequencekeys Get the array with each key matching the value set in the metadata field $iiif_sequence_field. By default the array will be sorted but have a 0 based index
|
||
*
|
||
* @return array
|
||
*/
|
||
function iiif_get_canvases($identifier, $iiif_results, $sequencekeys = false)
|
||
{
|
||
global $rooturl,$iiif_sequence_field;
|
||
|
||
$canvases = array();
|
||
foreach ($iiif_results as $index => $iiif_result) {
|
||
$useimage = $iiif_result;
|
||
if ((int)$iiif_result['has_image'] === 0) {
|
||
// If configured, try and use a preview from a related resource
|
||
debug("IIIF: No image for IIIF request - check for related resources");
|
||
$pullresource = related_resource_pull($iiif_result);
|
||
if ($pullresource !== false) {
|
||
$useimage = $pullresource;
|
||
}
|
||
}
|
||
$size = is_jpeg_extension($useimage["file_extension"] ?? "") ? "" : "hpr";
|
||
$useextension = strtolower((string) $useimage["file_extension"]) == "jpeg" ? $useimage["file_extension"] : "jpg";
|
||
$img_path = get_resource_path($useimage["ref"], true, $size, false, $useextension);
|
||
if (!file_exists($img_path)) {
|
||
continue;
|
||
}
|
||
$sequenceid = $iiif_result["iiif_position"];
|
||
$sequence_field = get_resource_type_field($iiif_sequence_field);
|
||
$sequence_prefix = "";
|
||
if (isset($GLOBALS["iiif_sequence_prefix"])) {
|
||
$sequence_prefix = $GLOBALS["iiif_sequence_prefix"] === "" ? $sequence_field["title"] . " " : $GLOBALS["iiif_sequence_prefix"];
|
||
}
|
||
|
||
$canvases[$index]["@id"] = $rooturl . $identifier . "/canvas/" . $index;
|
||
$canvases[$index]["@type"] = "sc:Canvas";
|
||
$canvases[$index]["label"] = $sequence_prefix . $sequenceid;
|
||
|
||
// Get the size of the images
|
||
$image_size = get_original_imagesize($useimage["ref"], $img_path);
|
||
$canvases[$index]["height"] = intval($image_size[2]);
|
||
$canvases[$index]["width"] = intval($image_size[1]);
|
||
|
||
// "If the largest image's dimensions are less than 1200 pixels on either edge, then the canvas dimensions
|
||
// should be double those of the image." - From http://iiif.io/api/presentation/2.1/#canvas
|
||
if ($image_size[1] < 1200 || $image_size[2] < 1200) {
|
||
$image_size[1] = $image_size[1] * 2;
|
||
$image_size[2] = $image_size[2] * 2;
|
||
}
|
||
|
||
$canvases[$index]["thumbnail"] = iiif_get_thumbnail($useimage["ref"]);
|
||
|
||
// Add image (only 1 per canvas currently supported)
|
||
$canvases[$index]["images"] = array();
|
||
$size_info = array(
|
||
'identifier' => $size,
|
||
'return_height_width' => false,
|
||
'original_file_extension' => $useextension
|
||
);
|
||
$canvases[$index]["images"][] = iiif_get_image($identifier, $useimage["ref"], $index, $size_info);
|
||
}
|
||
|
||
if ($sequencekeys) {
|
||
// keep the sequence identifiers as keys so a required canvas can be accessed by sequence id
|
||
return $canvases;
|
||
}
|
||
|
||
ksort($canvases);
|
||
$return = array();
|
||
foreach ($canvases as $canvas) {
|
||
$return[] = $canvas;
|
||
}
|
||
return $return;
|
||
}
|
||
|
||
/**
|
||
* Get thumbnail information for the specified resource id ready for IIIF JSON encoding
|
||
*
|
||
* @uses get_resource_path()
|
||
* @uses getimagesize()
|
||
*
|
||
* @param integer $resourceid Resource ID
|
||
*
|
||
* @return array
|
||
*/
|
||
function iiif_get_thumbnail($resourceid)
|
||
{
|
||
global $rootimageurl;
|
||
|
||
$img_path = get_resource_path($resourceid, true, 'thm', false);
|
||
if (!file_exists($img_path)) {
|
||
// If configured, try and use a preview from a related resource
|
||
$resdata = get_resource_data($resourceid);
|
||
$pullresource = related_resource_pull($resdata);
|
||
if ($pullresource !== false) {
|
||
$resourceid = $pullresource["ref"];
|
||
$img_path = get_resource_path($resourceid, true, "thm", false);
|
||
}
|
||
}
|
||
|
||
if (!file_exists($img_path)) {
|
||
return false;
|
||
}
|
||
|
||
$thumbnail = array();
|
||
$thumbnail["@id"] = $rootimageurl . $resourceid . "/full/thm/0/default.jpg";
|
||
$thumbnail["@type"] = "dctypes:Image";
|
||
|
||
// Get the size of the images
|
||
$GLOBALS["use_error_exception"] = true;
|
||
try {
|
||
list($tw,$th) = getimagesize($img_path);
|
||
$thumbnail["height"] = (int) $th;
|
||
$thumbnail["width"] = (int) $tw;
|
||
} catch (Exception $e) {
|
||
$returned_error = $e->getMessage();
|
||
debug("getThumbnail: Unable to get image size for file: $img_path - $returned_error");
|
||
// Use defaults
|
||
$thumbnail["height"] = 150;
|
||
$thumbnail["width"] = 150;
|
||
}
|
||
unset($GLOBALS["use_error_exception"]);
|
||
|
||
$thumbnail["format"] = "image/jpeg";
|
||
|
||
$thumbnail["service"] = array();
|
||
$thumbnail["service"]["@context"] = "http://iiif.io/api/image/2/context.json";
|
||
$thumbnail["service"]["@id"] = $rootimageurl . $resourceid;
|
||
$thumbnail["service"]["profile"] = "http://iiif.io/api/image/2/level1.json";
|
||
return $thumbnail;
|
||
}
|
||
|
||
/**
|
||
* Get the image for the specified identifier canvas and resource id
|
||
*
|
||
* @uses get_original_imagesize()
|
||
* @uses get_resource_path()
|
||
*
|
||
* @param integer $identifier IIIF identifier (this associates resources via the metadata field set as $iiif_identifier_field
|
||
* @param integer $resourceid Resource ID
|
||
* @param string $position The canvas identifier, i.e position in the sequence. If $iiif_sequence_field is defined
|
||
* @param array $size ResourceSpace size information. Required information: identifier and whether it
|
||
* requires to return height & width back (e.g annotations don't require it).
|
||
* Please note for the identifier - we use 'hpr' if the original file is not a JPG file it
|
||
* will be the value of this metadata field for the given resource
|
||
* Example:
|
||
* $size_info = array(
|
||
* 'identifier' => 'hpr',
|
||
* 'return_height_width' => true
|
||
* );
|
||
*
|
||
* @return array
|
||
*/
|
||
function iiif_get_image($identifier, $resourceid, $position, array $size_info)
|
||
{
|
||
global $rooturl,$rootimageurl;
|
||
|
||
// Quick validation of the size_info param
|
||
if (empty($size_info) || (!isset($size_info['identifier']) && !isset($size_info['return_height_width']))) {
|
||
return false;
|
||
}
|
||
|
||
$size = $size_info['identifier'];
|
||
$return_height_width = $size_info['return_height_width'];
|
||
|
||
$useextension = $size_info['original_file_extension'] ?? 'jpg';
|
||
$img_path = get_resource_path($resourceid, true, $size, false, $useextension);
|
||
if (!file_exists($img_path)) {
|
||
return false;
|
||
}
|
||
|
||
$image_size = get_original_imagesize($resourceid, $img_path);
|
||
|
||
$images = array();
|
||
$images["@context"] = "http://iiif.io/api/presentation/2/context.json";
|
||
$images["@id"] = $rooturl . $identifier . "/annotation/" . $position;
|
||
$images["@type"] = "oa:Annotation";
|
||
$images["motivation"] = "sc:painting";
|
||
|
||
$images["resource"] = array();
|
||
$images["resource"]["@id"] = $rootimageurl . $resourceid . "/full/max/0/default.jpg";
|
||
$images["resource"]["@type"] = "dctypes:Image";
|
||
$images["resource"]["format"] = "image/jpeg";
|
||
|
||
$images["resource"]["height"] = intval($image_size[2]);
|
||
$images["resource"]["width"] = intval($image_size[1]);
|
||
|
||
$images["resource"]["service"] = array();
|
||
$images["resource"]["service"]["@context"] = "http://iiif.io/api/image/2/context.json";
|
||
$images["resource"]["service"]["@id"] = $rootimageurl . $resourceid;
|
||
$images["resource"]["service"]["profile"] = "http://iiif.io/api/image/2/level1.json";
|
||
$images["on"] = $rooturl . $identifier . "/canvas/" . $position;
|
||
|
||
if ($return_height_width) {
|
||
$images["height"] = intval($image_size[2]);
|
||
$images["width"] = intval($image_size[1]);
|
||
}
|
||
|
||
return $images;
|
||
}
|
||
|
||
/**
|
||
* Handle a IIIF error.
|
||
*
|
||
* @param integer $errorcode The error code
|
||
* @param array $errors An array of errors
|
||
* @return void
|
||
*/
|
||
function iiif_error($errorcode = 404, $errors = array())
|
||
{
|
||
if (function_exists("http_response_code")) {
|
||
http_response_code($errorcode); # Send error status
|
||
}
|
||
echo json_encode($errors);
|
||
exit();
|
||
}
|
||
|
||
// End of IIIF v2.1 functions.
|
||
|
||
function is_power_of_two(int $x): bool
|
||
{
|
||
return $x > 0 && (($x & ($x - 1)) === 0);
|
||
}
|