diff --git a/README.md b/README.md index 28908c694..caf380265 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,47 @@ By default, objects with the [Fedora state](https://wiki.duraspace.org/display/F * A [detailed tutorial](https://github.com/Islandora/islandora/wiki/Multi-paged-Ingest-Forms) on extending the multi-page ingest forms is available on the Github (developers') Wiki. * Additional modules developed by members of the Islandora community to extend Islandora can be found on the curated [Islandora Awesome](https://github.com/Islandora-Labs/islandora_awesome) list. -## Documentation +### Image Server configuration + + +#### Drupal + +In Administration » Islandora » Image Server configuration + +Choose the type of image server (Djatoka or IIIF). + +Set the URL. + +#### Djatoka + +![Configuration](https://user-images.githubusercontent.com/2857697/63660444-1a24c400-c77c-11e9-831d-5f3fc71b085e.png) + +#### IIIF + +![Configuration](https://user-images.githubusercontent.com/2857697/63660476-43455480-c77c-11e9-8460-c3d2639e7575.png) + +If using IIIF choose to send token as a header and choose the token to use. + +Any [IIIF](http://iiif.io) image server can be used with the IIIF tile source. The IIIF tile source provides a templated identifier which the IIIF server must be configured to resolve. This could be the full URL to the datastream to be displayed or something more complex. + +The [Cantaloupe 🍈](https://medusa-project.github.io/cantaloupe/) IIIF image server can be configured to resolve these identifiers using the [`HttpResolver`](https://medusa-project.github.io/cantaloupe/manual/3.3/resolvers.html#HttpResolver) with no prefix specified. + +#### Apache Reverse Proxy + +Reverse proxy config: We make the assumption that we (reverse) proxy Djatoka, to fix the same-origin issue. + +For Apache, with Drupal running on the same box as Apache, a couple lines like: + +``` +ProxyPass /adore-djatoka http://localhost:8080/adore-djatoka +ProxyPassReverse /adore-djatoka http://localhost:8080/adore-djatoka +``` + +in the Apache config somewhere (either the main apache.conf, httpd.conf, or in and arbitrarily named `*.conf` in your Apache's conf.d directory should suffice to establish the reverse proxy. + +In Debian derived systems one will need to create location entries for each proxy or remove the Deny from All in mod_proxy's conf file. + +# Documentation Further documentation for this module is available at [our documentation wiki](https://wiki.duraspace.org/display/ISLANDORA/Islandora+Core+Module). diff --git a/includes/dublin_core.inc b/includes/dublin_core.inc index cff1bebed..924d97067 100644 --- a/includes/dublin_core.inc +++ b/includes/dublin_core.inc @@ -13,6 +13,11 @@ */ class DublinCore { + /** + * Dublin Core fields. + * + * @var array|\DublinCore + */ public $dc = array( 'dc:title' => array(), 'dc:creator' => array(), @@ -30,6 +35,12 @@ class DublinCore { 'dc:coverage' => array(), 'dc:rights' => array(), ); + + /** + * Owner name. + * + * @var string + */ public $owner; /** diff --git a/includes/imageserver.inc b/includes/imageserver.inc new file mode 100644 index 000000000..f1d55ccfc --- /dev/null +++ b/includes/imageserver.inc @@ -0,0 +1,335 @@ + islandora_imageserver_get_type_selectbox(FALSE), + 'url' => array( + '#prefix' => '
', + '#suffix' => '
', + '#type' => 'textfield', + '#title' => t('Image Server Base URL'), + '#title_display' => 'invisible', + '#default_value' => $settings['url'], + '#description' => t('The location of the image server.
!confirmation_message', array( + '!confirmation_message' => islandora_imageserver_admin_form_access_message($form_state), + )), + '#ajax' => array( + 'callback' => 'islandora_imageserver_admin_ajax_url', + 'wrapper' => 'islandora-imageserver-path-wrapper', + ), + ), + 'iiif' => array( + '#type' => 'fieldset', + '#title' => t('IIIF Image Server Settings'), + '#description' => t('Settings for IIIF Image Server'), + '#states' => array( + 'visible' => array( + ':input[name="type"]' => array('value' => 'iiif'), + ), + ), + 'iiif_token_header' => array( + '#type' => 'checkbox', + '#title' => t('Add token as header'), + '#default_value' => $settings['iiif_token_header'], + '#description' => t('Instead of sending the token as a query parameter, it will be sent in the X-ISLANDORA-TOKEN header.'), + ), + 'iiif_identifier' => array( + '#type' => 'textfield', + '#title' => t('IIIF Identifier'), + '#default_value' => $settings['iiif_identifier'], + '#element_validate' => array('token_element_validate'), + '#token_types' => array('islandora'), + ), + 'islandora_imageserver_iiif_token_tree' => array( + '#type' => 'fieldset', + '#title' => t('Replacement patterns'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => theme('token_tree', array( + 'token_types' => array('islandora'), + 'global_types' => FALSE, + )), + ), + ), + 'actions' => array( + '#type' => 'actions', + 'save' => array( + '#type' => 'submit', + '#value' => t('Save configuration'), + '#weight' => 0, + ), + 'reset' => array( + '#type' => 'submit', + '#value' => t('Reset to defaults'), + '#weight' => 1, + '#submit' => array('islandora_imageserver_admin_submit_reset'), + ), + ), + ); + return $form; +} + +/** + * Redirect to Reset form. + * + * @param array $form + * Drupal form. + * @param array $form_state + * Drupal form state. + */ +function islandora_imageserver_admin_submit_reset(array $form, array &$form_state) { + $form_state['redirect'] = 'admin/islandora/image_server/reset'; +} + +/** + * Settings reset confirmation form. + * + * @param array $form + * The Drupal form. + * @param array $form_state + * The Drupal $form_state. + * + * @return mixed + * The created form. + */ +function islandora_imageserver_admin_reset_confirm_form(array $form, array &$form_state) { + return confirm_form( + $form, + t("Do you really want to reset the Image Server settings?"), + 'admin/islandora/image_server', + t("This action cannot be undone."), + t("Reset") + ); +} + +/** + * Implements hook_form_submit(). + */ +function islandora_imageserver_admin_reset_confirm_form_submit(array $form, array &$form_state) { + variable_del('islandora_imageserver_settings'); + drupal_set_message(t('Settings reset.'), 'status'); + $form_state['redirect'] = 'admin/islandora/image_server'; +} + +/** + * Implements hook_form_submit(). + */ +function islandora_imageserver_admin_form_submit(array $form, array &$form_state) { + $type = $form_state['values']['type']; + $settings = islandora_imageserver_get_settings(); + $settings['type'] = $type; + $settings['url'] = rtrim($form_state['values']['url'], '/'); + if ($type == 'iiif') { + $settings['iiif_token_header'] = (bool) $form_state['values']['iiif_token_header']; + $settings['iiif_identifier'] = $form_state['values']['iiif_identifier']; + } + else { + $settings['iiif_token_header'] = FALSE; + $settings['iiif_identifier'] = ISLANDORA_IMAGESERVER_DEFAULT_TOKEN; + } + variable_set('islandora_imageserver_settings', $settings); + drupal_set_message(t("Settings saved successfully."), 'status'); +} + +/** + * Callback to check URL. + * + * @param array $form + * Drupal form. + * @param array $form_state + * Drupal form state. + * + * @return mixed + * The form element. + */ +function islandora_imageserver_admin_ajax_url(array $form, array $form_state) { + return $form['url']; +} + +/** + * Gets a message which describes if Adore-Djatoka is accessible. + * + * @param array $form_state + * The current form state. + * + * @see islandora_imageserver_get_settings() + * + * @return string + * A message describing the accessibility of the Adore-Djatoka image resolver. + */ +function islandora_imageserver_admin_form_access_message(array &$form_state) { + $confirmation_message = ''; + + $type = islandora_imageserver_get_default_value($form_state, 'type'); + $url = islandora_imageserver_get_default_value($form_state, 'url'); + + if ($type == 'djatoka') { + $url = url($url, array( + 'absolute' => TRUE, + 'query' => array( + 'url_ver' => 'Z39.88-2004', + 'rft_id' => 'http://memory.loc.gov/gmd/gmd433/g4330/g4330/np000066.jp2', + 'svc_id' => 'info:lanl-repo/svc/getRegion', + 'svc_val_fmt' => 'info:ofi/fmt:kev:mtx:jpeg2000', + 'svc.format' => 'image/jpeg', + 'svc.level' => '1', + ), + )); + } + elseif ($type == 'iiif') { + $url = url(rtrim($url, '/'), + array( + 'absolute' => TRUE, + ) + ); + } + else { + // None, so don't confirm. + return ""; + } + + if (isset($url) && !empty($url)) { + $result = drupal_http_request($url); + if ($result->code == 200) { + $confirmation_message = theme_image(array( + 'path' => 'misc/watchdog-ok.png', + 'attributes' => array(), + )); + $confirmation_message .= t('Successfully connected to image server.'); + } + else { + $confirmation_message = theme_image(array( + 'path' => 'misc/watchdog-error.png', + 'attributes' => array(), + )); + $confirmation_message .= t('Unable to connect to image server at !path', array( + '!path' => $url, + )); + } + } + return $confirmation_message; +} + +/** + * Get image server settings. + * + * @return array + * Configuration to access the image server. + */ +function islandora_imageserver_get_settings() { + $defaults = islandora_imageserver_get_default_settings(); + + $settings = variable_get('islandora_imageserver_settings', array()) + $defaults; + + return $settings; +} + +/** + * Return the default image server settings. + * + * @return array + * The settings. + */ +function islandora_imageserver_get_default_settings() { + return array( + 'type' => 'none', + 'url' => '', + 'iiif_token_header' => FALSE, + 'iiif_identifier' => ISLANDORA_IMAGESERVER_DEFAULT_TOKEN, + ); +} + +/** + * Utility to check form_state or return the default. + * + * @param array $form_state + * Drupal form state. + * @param string $name + * Name of the form element/settings key. + * + * @return mixed + * The Form value or currently saved value. + */ +function islandora_imageserver_get_default_value(array &$form_state, $name) { + $settings = islandora_imageserver_get_settings(); + return isset($form_state['values'][$name]) ? $form_state['values'][$name] : $settings[$name]; +} + +/** + * Generate a replacement string using tokens. + * + * @param string $string_token + * The replacement token to generate. + * @param string $pid + * The pid of the object. + * @param string $dsid + * The dsid to return. + * @param string $authtoken + * The authentication token. + * + * @return mixed + * The token replaced string. + */ +function islandora_imageserver_get_identifier($string_token, $pid, $dsid, $authtoken) { + $settings = islandora_imageserver_get_settings(); + if ($settings['type'] == 'djatoka') { + // We use the token_replace to generate the rft_id. + $string_token = '[islandora:url_token]'; + } + $parts = array( + 'islandora' => array( + 'pid' => $pid, + 'dsid' => $dsid, + 'token' => $authtoken, + ), + ); + return token_replace($string_token, $parts); +} + +/** + * Create the select box form element with the currently configured type. + * + * @param bool $disabled + * Whether the element should be disabled. + * + * @return array + * The form element. + */ +function islandora_imageserver_get_type_selectbox($disabled = TRUE) { + $settings = islandora_imageserver_get_settings(); + $description = $disabled ? t("Configured at Admin ≫ Islandora ≫ Image server configuration.") : t("Select the image server to configure."); + return array( + '#type' => 'select', + '#title' => t('Image Server'), + '#description' => $description, + '#default_value' => $settings['type'], + '#disabled' => $disabled, + '#options' => array( + 'none' => t('No image server configured'), + 'iiif' => t('IIIF image server'), + 'djatoka' => t('Adore-Djatoka image server'), + ), + ); +} diff --git a/includes/mime_detect.inc b/includes/mime_detect.inc index 818e150be..9baedbc2a 100644 --- a/includes/mime_detect.inc +++ b/includes/mime_detect.inc @@ -59,7 +59,17 @@ class MimeDetect { * $this->get_extension('image/jpeg') will always return 'jpg'. */ protected $protectedMimeTypes = array(); + /** + * Protected file extensions. + * + * @var array + */ protected $protectedFileExtensions; + /** + * Extension exceptions. + * + * @var array + */ protected $extensionExceptions = array( // XXX: Deprecated... Only here due to old 'tif' => 'image/tif' mapping... // The correct MIMEtype is 'image/tiff'. diff --git a/includes/tuque_wrapper.inc b/includes/tuque_wrapper.inc index 3b08a9fda..285cecfa9 100644 --- a/includes/tuque_wrapper.inc +++ b/includes/tuque_wrapper.inc @@ -80,8 +80,23 @@ function islandora_invoke_datastream_hooks($hook, array $models, $dsid) { * Implementation of the FedoraRepository class. */ class IslandoraFedoraRepository extends FedoraRepository { + /** + * Class name. + * + * @var string + */ protected $queryClass = 'IslandoraRepositoryQuery'; + /** + * Class name. + * + * @var string + */ protected $newObjectClass = 'IslandoraNewFedoraObject'; + /** + * Class name. + * + * @var string + */ protected $objectClass = 'IslandoraFedoraObject'; /** @@ -179,8 +194,23 @@ class IslandoraRepositoryQuery extends RepositoryQuery {} * Implementation of NewFedoraObject class. */ class IslandoraNewFedoraObject extends NewFedoraObject { + /** + * Class name. + * + * @var string + */ protected $newFedoraDatastreamClass = 'IslandoraNewFedoraDatastream'; + /** + * Class name. + * + * @var string + */ protected $fedoraDatastreamClass = 'IslandoraFedoraDatastream'; + /** + * Class name. + * + * @var string + */ protected $fedoraRelsExtClass = 'IslandoraFedoraRelsExt'; } @@ -188,8 +218,23 @@ class IslandoraNewFedoraObject extends NewFedoraObject { * Implementation, magic functions for a FedoraObject class. */ class IslandoraFedoraObject extends FedoraObject { + /** + * Class name. + * + * @var string + */ protected $newFedoraDatastreamClass = 'IslandoraNewFedoraDatastream'; + /** + * Class name. + * + * @var string + */ protected $fedoraDatastreamClass = 'IslandoraFedoraDatastream'; + /** + * Class name. + * + * @var string + */ protected $fedoraRelsExtClass = 'IslandoraFedoraRelsExt'; /** @@ -568,7 +613,17 @@ class IslandoraSimpleCache extends SimpleCache {} * Implementation of NewFedoraDatastream class. */ class IslandoraNewFedoraDatastream extends NewFedoraDatastream { + /** + * Class name. + * + * @var string + */ protected $fedoraRelsIntClass = 'IslandoraFedoraRelsInt'; + /** + * Class name. + * + * @var string + */ protected $fedoraDatastreamVersionClass = 'IslandoraFedoraDatastreamVersion'; } @@ -576,7 +631,17 @@ class IslandoraNewFedoraDatastream extends NewFedoraDatastream { * Implementation and magic functions for FedoraDatastream class. */ class IslandoraFedoraDatastream extends FedoraDatastream { + /** + * Class name. + * + * @var string + */ protected $fedoraRelsIntClass = 'IslandoraFedoraRelsInt'; + /** + * Class name. + * + * @var string + */ protected $fedoraDatastreamVersionClass = 'IslandoraFedoraDatastreamVersion'; /** @@ -650,7 +715,17 @@ class IslandoraFedoraDatastream extends FedoraDatastream { * Implementation of FedoraDatastreamVersion class. */ class IslandoraFedoraDatastreamVersion extends FedoraDatastreamVersion { + /** + * Class name. + * + * @var string + */ protected $fedoraRelsIntClass = 'IslandoraFedoraRelsInt'; + /** + * Class name. + * + * @var string + */ protected $fedoraDatastreamVersionClass = 'IslandoraFedoraDatastreamVersion'; } diff --git a/includes/utilities.inc b/includes/utilities.inc index b14053304..10ac2d1f3 100644 --- a/includes/utilities.inc +++ b/includes/utilities.inc @@ -267,9 +267,9 @@ function islandora_escape_pid_for_function($pid) { * below are fine. * * @code - * 'islandora', - * 'islandora:', - * 'islandora:1234', + * 'islandora', + * 'islandora:', + * 'islandora:1234', * @endcode * * @return string @@ -291,9 +291,9 @@ function islandora_get_namespace($id) { * below are fine. * * @code - * 'islandora', - * 'islandora:', - * 'islandora:1234', + * 'islandora', + * 'islandora:', + * 'islandora:1234', * @endcode * * @return bool diff --git a/islandora.install b/islandora.install index cc0cc49fc..357c8c1fd 100644 --- a/islandora.install +++ b/islandora.install @@ -61,6 +61,7 @@ function islandora_uninstall() { 'islandora_breadcrumbs_backends', 'islandora_render_context_ingeststep', 'islandora_deny_inactive_and_deleted', + 'islandora_imageserver_settings', ); array_walk($variables, 'variable_del'); } @@ -144,3 +145,68 @@ function islandora_update_7002() { // Removing as the deprecation has been removed. variable_del('islandora_deprecation_return_false_when_datastream_exists'); } + +/** + * Implements hook_update_N(). + * + * Try to auto-configure the image server settings in core based on + * the Internet Archive Bookreader and/or Openseadragon settings. + */ +function islandora_update_7003() { + module_load_include('inc', 'islandora', 'includes/imageserver'); + if (islandora_imageserver_get_settings()['type'] != 'none') { + return t('Settings have already been configured manually.'); + } + if (module_exists('islandora_openseadragon')) { + $openseadragon_settings = array( + 'type' => variable_get('islandora_openseadragon_tilesource', 'djatoka'), + ); + if ($openseadragon_settings['type'] == 'djatoka') { + $openseadragon_settings['url'] = variable_get('islandora_openseadragon_djatoka_url', 'adore-djatoka/resolver'); + } + else { + $openseadragon_settings['url'] = variable_get('islandora_openseadragon_iiif_url', 'iiif'); + $openseadragon_settings['iiif_token_header'] = variable_get('islandora_openseadragon_iiif_token_header', FALSE); + $openseadragon_settings['iiif_identifier'] = str_replace('[islandora_openseadragon:', '[islandora:', variable_get('islandora_openseadragon_iiif_identifier', '[islandora_openseadragon:url_token]')); + } + } + if (module_exists('islandora_internet_archive_bookreader')) { + $iabv_settings = array( + 'type' => variable_get('islandora_internet_archive_bookreader_pagesource', 'djatoka'), + ); + if ($iabv_settings['type'] == 'djatoka') { + $iabv_settings['url'] = variable_get('islandora_paged_content_djatoka_url', 'http://localhost:8080/adore-djatoka'); + } + else { + $iabv_settings['url'] = variable_get('islandora_internet_archive_bookreader_iiif_url', 'iiif'); + $iabv_settings['iiif_token_header'] = variable_get('islandora_internet_archive_bookreader_iiif_token_header', FALSE); + $iabv_settings['iiif_identifier'] = str_replace('[islandora_iareader:', '[islandora:', variable_get('islandora_internet_archive_bookreader_iiif_identifier', '[islandora_iareader:url_token]')); + } + } + if (isset($openseadragon_settings) && isset($iabv_settings)) { + if (count(array_diff($openseadragon_settings, $iabv_settings)) == 0) { + // Settings are the same, use for core imageserver variables. + $new_settings = $openseadragon_settings; + } + else { + // We could not migrate your settings to the new place. This stops the + // other two modules from deleting your settings. + throw new DrupalUpdateException('Islandora image server settings could NOT be configured automatically, please set them manually at Admin -> Islandora -> Image Server configuration and then run updates again.'); + } + } + elseif (isset($openseadragon_settings)) { + $new_settings = $openseadragon_settings; + } + elseif (isset($iabv_settings)) { + $new_settings = $iabv_settings; + } + if (isset($new_settings)) { + if ($new_settings['type'] == 'djatoka' && !preg_match('~resolver$~', $new_settings['url'])) { + // IABV javascript added resolver to URL, Openseadragon did not. + $new_settings['url'] = rtrim($new_settings['url'], '/') . '/resolver'; + } + // We have migrated your settings to the new place. + variable_set('islandora_imageserver_settings', $new_settings); + return t("Islandora image server settings configured automatically."); + } +} diff --git a/islandora.module b/islandora.module index 3e53ebac0..0e8b3d12e 100644 --- a/islandora.module +++ b/islandora.module @@ -72,6 +72,8 @@ define('ISLANDORA_MIME_TYPES_AUTOCOMPLETE', 'islandora/autocomplete/mime-types') const ISLANDORA_BREADCRUMB_LEGACY_BACKEND = 'islandora_breadcrumbs_legacy_sparql'; +const ISLANDORA_IMAGESERVER_DEFAULT_TOKEN = "[islandora:url_token]"; + /** * Implements hook_menu(). * @@ -417,6 +419,24 @@ function islandora_menu() { 'type' => MENU_SUGGESTED_ITEM, 'access arguments' => array(ISLANDORA_MANAGE_DELETED_OBJECTS), ); + $items['admin/islandora/image_server'] = array( + 'title' => 'Image server configuration', + 'description' => 'Define the settings for your local image server', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('islandora_imageserver_admin_form'), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_NORMAL_ITEM, + 'file' => 'includes/imageserver.inc', + ); + $items['admin/islandora/image_server/reset'] = array( + 'title' => 'Image server configuration reset confirmation', + 'description' => 'Image server configuration reset confirmation', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('islandora_imageserver_admin_reset_confirm_form'), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK, + 'file' => 'includes/imageserver.inc', + ); return $items; } @@ -2230,3 +2250,90 @@ function islandora_islandora_solution_pack_child_relationships($cmodels) { // exist when the hook is called, even if no module responds. return array('predicate' => array(), 'prefix' => array()); } + +/** + * Implements hook_token_info(). + */ +function islandora_token_info() { + $info = array(); + + $info['types']['islandora'] = array( + 'name' => t('Islandora'), + 'description' => t('Tokens for building IIIF identifer in Islandora.'), + 'needs-data' => 'islandora', + ); + + $info['tokens']['islandora']['pid'] = array( + 'name' => t('PID'), + 'description' => t('The objects PID.'), + ); + + $info['tokens']['islandora']['dsid'] = array( + 'name' => t('DSID'), + 'description' => t('The objects DSID.'), + ); + + $info['tokens']['islandora']['url'] = array( + 'name' => t('URL'), + 'description' => t('The URL to the object in Islandora.'), + ); + + $info['tokens']['islandora']['url_token'] = array( + 'name' => t('URL with Token'), + 'description' => t('The URL to the object in Islandora with token in the query string.'), + ); + + $info['tokens']['islandora']['token'] = array( + 'name' => t('Token'), + 'description' => t('The token that can be used to access the object in Islandora.'), + ); + + return $info; +} + +/** + * Implements hook_tokens(). + */ +function islandora_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + + if ($type != 'islandora' || !isset($data['islandora'])) { + return $replacements; + } + + $pid = $data['islandora']['pid']; + $dsid = $data['islandora']['dsid']; + $token = $data['islandora']['token']; + + foreach ($tokens as $name => $original) { + if ($name == 'pid') { + $replacements[$original] = $pid; + } + elseif ($name == 'dsid') { + $replacements[$original] = $dsid; + } + elseif ($name == 'token') { + $replacements[$original] = $token; + } + elseif ($name == 'url' || $name == 'url_token') { + $options = array( + 'absolute' => TRUE, + 'language' => language_default(), + 'https' => (function_exists('drupal_is_https') ? + drupal_is_https() : + (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') + ), + ); + + if ($name == 'url_token') { + $options['query'] = array( + 'token' => $token, + ); + } + + $replacements[$original] = url("islandora/object/{$pid}/datastream/{$dsid}/view", $options); + } + } + + return $replacements; +} diff --git a/tests/includes/utilities.inc b/tests/includes/utilities.inc index 2549d21b5..c90291390 100644 --- a/tests/includes/utilities.inc +++ b/tests/includes/utilities.inc @@ -19,12 +19,32 @@ */ class IslandoraTestUtilities extends IslandoraTestUtilityClass { + /** + * The test configuration. + * + * @var array + */ protected $configuration; + /** + * The parameters. + * + * @var array + */ protected $params; + /** + * Test results. + * + * @var array + */ public $results = array(); + /** + * A repository object. + * + * @var \FedoraRepository + */ protected $repository; /**