diff --git a/.travis.yml b/.travis.yml index 17a22707..660fb448 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ branches: env: - FEDORA_VERSION="3.5" - FEDORA_VERSION="3.6.2" + - FEDORA_VERSION="3.7.0" before_install: - export ISLANDORA_DIR=$TRAVIS_BUILD_DIR - $TRAVIS_BUILD_DIR/tests/scripts/travis_setup.sh diff --git a/README.md b/README.md index 798e6b33..90d935cf 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,17 @@ REQUIREMENTS The Tuque library must be installed to use Islandora. It can be found here: http://github.com/Islandora/tuque It is expected to be in one of two paths: - - sites/all/libraries/tuque (libraries directory may need to be created) + - sites/all/libraries/tuque (libraries directory may need to be created) - islandora_folder/libraries/tuque +OPTIONAL REQUIREMENTS +--------------------- + +If you want to support languages other than English download and enable +[String Translation](https://drupal.org/project/i18n), And follow our +[guide](wiki/Multilingual-Support) for setting up additional languges. + + INSTALLATION ------------ @@ -36,6 +44,10 @@ Before installing Islandora the XACML policies located in the policies folder should be copied into the Fedora global XACML policies folder. This will allow "authenticated users" in Drupal to access Fedora API-M functions. +You will also have to remove some default policies if you want full functionality as well. + +Remove deny-purge-datastream-if-active-or-inactive.xml to allow for purging of datastream versions. + CONFIGURATION ------------- diff --git a/build.xml b/build.xml index 273f8ca8..9c52ceb8 100644 --- a/build.xml +++ b/build.xml @@ -1,7 +1,7 @@ - + @@ -9,6 +9,7 @@ + @@ -17,6 +18,13 @@ + + + + + + + @@ -81,4 +89,17 @@ + + + + + + + + + + + + + diff --git a/css/islandora.base.css b/css/islandora.base.css index 585ad756..28301aa1 100644 --- a/css/islandora.base.css +++ b/css/islandora.base.css @@ -1,4 +1,4 @@ -/* +/* Document : islandora_basic_collection.base.css Created on : May 23, 2012, 11:22:04 AM Description: @@ -19,7 +19,7 @@ /* * These rules will display DTs/DDs as columns. * Constructs must follow a key/value pair pattern. - * The three last declarations are meant to kill white space between DTs/DDs + * The three last declarations are meant to kill white space between DTs/DDs * (result of inline-block styling) */ @@ -43,7 +43,20 @@ dl.islandora-inline-metadata { padding-left: 40px; } -/* +dl.islandora-metadata-fields { + width:100%; +} + +.islandora-metadata dt, +.islandora-metadata dd { + border-top:1px solid #e5e5e5; +} + +.islandora-metadata dt.first, +.islandora-metadata dd.first { + border-top:0; +} +/* * In this rule, we reset the white-space (see hack above) */ .islandora-inline-metadata dt, diff --git a/css/islandora.print.css b/css/islandora.print.css new file mode 100644 index 00000000..4b714bc5 --- /dev/null +++ b/css/islandora.print.css @@ -0,0 +1,64 @@ +/** + * @file + * Print styling + * + * We provide some sane print styling for Drupal, hiding most visuals. + */ + +#content a[href^="javascript:"]:after, +#content a[href^="#"]:after { /* Only display useful links. */ +/* content: ""; */ +} + +#content abbr[title]:after { /* Add visible title after abbreviations. */ +/* content: " (" attr(title) ")"; */ +} + +#content { + left: 0 !important; + width: 100% !important; +} +uncomment when ready to test printing +#header { + display: none !important; +} + +#content { /* Un-float the content */ + float: none !important; + width: 100% !important; + margin: 0 !important; + padding: 0 !important; +} + +body, +#page, +#main, +#content { /* Turn off any background colors or images */ + color: #000; + background-color: transparent !important; + background-image: none !important; +} + +body.sidebar-first { + left: 0 !important; + width: 100% !important; +} +#skip-link, +#toolbar, +#navigation, +/* .region-sidebar-first, */ +/* .region-sidebar-second, */ +#header, +#footer, +.breadcrumb, +.tabs, +.action-links, +.links, +.book-navigation, +.forum-topic-navigation, +.pager, +.contextual-links-region, +.feed-icons { /* Hide sidebars and nav elements */ + visibility: hidden !important; + display: none !important; +} diff --git a/css/islandora.theme.css b/css/islandora.theme.css index 29f14e66..4ac4fddc 100644 --- a/css/islandora.theme.css +++ b/css/islandora.theme.css @@ -1,4 +1,4 @@ -/* +/* Document : islandora_basic_collection.theme Created on : May 23, 2012, 11:23:56 AM Description: @@ -11,16 +11,16 @@ dl.islandora-object-tn { float: left; - width: 20.8333%; - padding: 0 10px 0 0; margin: 1.5em 0; + padding: 0 10px 0 0; + width: 20.8333%; } dl.islandora-object-fields { + border-top: 0px solid #ddd; float: right; - width:79.1666%; - border-top:0px solid #ddd; margin: 1.5em 0; + width: 79.1666%; } .islandora-object-fields dt { @@ -28,16 +28,16 @@ dl.islandora-object-fields { } .islandora-object-fields dt.first { - border-top:0; + border-top: 0; } .islandora-object-fields dt, .islandora-object-fields dd { - padding:6px 2% 4px; - border-top:1px solid #ddd; + border-top: 1px solid #ddd; + padding: 6px 2% 4px; } .islandora-object-fields dt.first, .islandora-object-fields dd.first { border-top: 0; -} \ No newline at end of file +} diff --git a/images/print-icon.png b/images/print-icon.png new file mode 100644 index 00000000..5f5a3b3c Binary files /dev/null and b/images/print-icon.png differ diff --git a/includes/add_datastream.form.inc b/includes/add_datastream.form.inc index 62490c9a..33decefc 100644 --- a/includes/add_datastream.form.inc +++ b/includes/add_datastream.form.inc @@ -32,7 +32,7 @@ function islandora_add_datastream_form(array $form, array &$form_state, Abstract '#attributes' => array( 'enctype' => 'multipart/form-data', ), - 'dsid' => array( + 'dsid_fieldset' => array( '#type' => 'fieldset', '#title' => 'Add a datastream', '#collapsible' => FALSE, @@ -40,44 +40,44 @@ function islandora_add_datastream_form(array $form, array &$form_state, Abstract 'dsid' => array( '#title' => 'Datastream ID', '#description' => t("An ID for this stream that is unique to this object. Must start with a letter and contain only alphanumeric characters, dashes and underscores. The following datastreams are defined by this content model but don't currently exist: @unused_dsids.", array('@unused_dsids' => $unused_datastreams)), + '#type' => 'textfield', + '#size' => 64, + '#maxlength' => 64, + '#required' => TRUE, + '#element_validate' => array( + 'islandora_add_datastream_form_field_is_not_an_existing_datastream_id', + 'islandora_add_datastream_form_field_starts_with_a_letter', + 'islandora_add_datastream_form_field_is_valid_dsid', + ), + '#autocomplete_path' => "islandora/object/{$object->id}/manage/datastreams/add/autocomplete", ), - '#type' => 'textfield', - '#size' => 64, - '#maxlength' => 64, - '#required' => TRUE, - '#element_validate' => array( - 'islandora_add_datastream_form_field_is_not_an_existing_datastream_id', - 'islandora_add_datastream_form_field_starts_with_a_letter', - 'islandora_add_datastream_form_field_is_valid_dsid', + 'label' => array( + '#title' => 'Datastream Label', + '#required' => TRUE, + '#size' => 64, + '#maxlength' => 64, + '#description' => t('A human-readable label'), + '#type' => 'textfield', + '#element_validate' => array('islandora_add_datastream_form_field_does_not_contain_a_forward_slash'), ), - '#autocomplete_path' => "islandora/object/{$object->id}/manage/datastreams/add/autocomplete", - ), - 'label' => array( - '#title' => 'Datastream Label', - '#required' => TRUE, - '#size' => 64, - '#maxlength' => 64, - '#description' => t('A human-readable label'), - '#type' => 'textfield', - '#element_validate' => array('islandora_add_datastream_form_field_does_not_contain_a_forward_slash'), - ), - 'file' => array( - '#type' => 'managed_file', - '#required' => TRUE, - '#title' => t('Upload Document'), - '#size' => 48, - '#description' => t('Select a file to upload.
Files must be less than @size MB.', array('@size' => $upload_size)), - '#default_value' => isset($form_state['values']['files']) ? $form_state['values']['files'] : NULL, - '#upload_location' => 'temporary://', - '#upload_validators' => array( - 'file_validate_extensions' => array(NULL), - // Assume its specified in MB. - 'file_validate_size' => array($upload_size * 1024 * 1024), + 'file' => array( + '#type' => 'managed_file', + '#required' => TRUE, + '#title' => t('Upload Document'), + '#size' => 48, + '#description' => t('Select a file to upload.
Files must be less than @size MB.', array('@size' => $upload_size)), + '#default_value' => isset($form_state['values']['files']) ? $form_state['values']['files'] : NULL, + '#upload_location' => 'temporary://', + '#upload_validators' => array( + 'file_validate_extensions' => array(NULL), + // Assume its specified in MB. + 'file_validate_size' => array($upload_size * 1024 * 1024), + ), + ), + 'submit' => array( + '#type' => 'submit', + '#value' => t('Add Datastream'), ), - ), - 'submit' => array( - '#type' => 'submit', - '#value' => t('Add Datastream'), ), ); } @@ -148,7 +148,7 @@ function islandora_add_datastream_form_field_does_not_contain_a_forward_slash(ar } /** - * Checks if the given datastream requires the upload to be a certian MIME type. + * Checks if the given datastream requires the upload to be a certain MIME type. * * @param array $form * The Drupal form. @@ -161,7 +161,10 @@ function islandora_add_datastream_form_validate(array $form, array &$form_state) if (isset($form_state['datastream_requirements'][$dsid]) && $file) { $allowed_types = $form_state['datastream_requirements'][$dsid]['mime']; $mime_detect = new MimeDetect(); - $allowed_extensions = array_map(array($mime_detect, 'getExtension'), $allowed_types); + $allowed_extensions = array(); + foreach ($allowed_types as $mime) { + $allowed_extensions = array_merge($allowed_extensions, $mime_detect->getValidExtensions($mime)); + } $errors = file_validate_extensions($file, implode(' ', $allowed_extensions)); if (count($errors) > 0) { form_set_error('file', $errors[0]); diff --git a/includes/admin.form.inc b/includes/admin.form.inc index d34bd5d5..b0bb0642 100644 --- a/includes/admin.form.inc +++ b/includes/admin.form.inc @@ -55,6 +55,12 @@ function islandora_repository_admin(array $form, array &$form_state) { '#description' => t('The PID of the Root Collection Object'), '#required' => TRUE, ), + 'islandora_use_datastream_cache_headers' => array( + '#type' => 'checkbox', + '#title' => t('Generate/parse datastream HTTP cache headers'), + '#description' => t('HTTP caching can reduce network traffic, by allowing clients to used cached copies.'), + '#default_value' => variable_get('islandora_use_datastream_cache_headers', TRUE), + ), ), 'islandora_namespace' => array( '#type' => 'fieldset', diff --git a/includes/authtokens.inc b/includes/authtokens.inc index 9d5783a9..36e8689f 100644 --- a/includes/authtokens.inc +++ b/includes/authtokens.inc @@ -10,7 +10,7 @@ // Token lifespan(seconds): after this duration the token expires. // 5 minutes. -define('TOKEN_TIMEOUT', 300); +define('ISLANDORA_AUTHTOKEN_TOKEN_TIMEOUT', 300); /** * Request Islandora to construct an object/datastream authentication token. @@ -92,7 +92,7 @@ function islandora_validate_object_token($pid, $dsid, $token) { ->condition('pid', $pid, '=') ->condition('dsid', $dsid, '=') ->condition('time', $time, '<=') - ->condition('time', $time - TOKEN_TIMEOUT, '>') + ->condition('time', $time - ISLANDORA_AUTHTOKEN_TOKEN_TIMEOUT, '>') ->execute() ->fetchAll(); if ($result) { @@ -131,6 +131,6 @@ function islandora_validate_object_token($pid, $dsid, $token) { function islandora_remove_expired_tokens() { $time = time(); db_delete("islandora_authtokens") - ->condition('time', $time - TOKEN_TIMEOUT, '<') + ->condition('time', $time - ISLANDORA_AUTHTOKEN_TOKEN_TIMEOUT, '<') ->execute(); } diff --git a/includes/breadcrumb.inc b/includes/breadcrumb.inc index 872eb70a..05ab1bac 100644 --- a/includes/breadcrumb.inc +++ b/includes/breadcrumb.inc @@ -27,6 +27,8 @@ function islandora_get_breadcrumbs($object) { $breadcrumbs = islandora_get_breadcrumbs_recursive($object->id, $object->repository); array_pop($breadcrumbs); + $context = 'islandora'; + drupal_alter('islandora_breadcrumbs', $breadcrumbs, $context); return $breadcrumbs; } diff --git a/includes/datastream.inc b/includes/datastream.inc index dc01bdfe..1d506e3d 100644 --- a/includes/datastream.inc +++ b/includes/datastream.inc @@ -26,10 +26,26 @@ function islandora_download_datastream(AbstractDatastream $datastream) { * @param bool $download * If TRUE the file is download to the user computer for viewing otherwise it * will attempt to display in the browser natively. + * @param int $version + * The version of the datastream to display */ -function islandora_view_datastream(AbstractDatastream $datastream, $download = FALSE) { - header_remove('Cache-Control'); - header_remove('Expires'); +function islandora_view_datastream(AbstractDatastream $datastream, $download = FALSE, $version = NULL) { + // XXX: Certain features of the Devel module rely on the use of "shutdown + // handlers", such as query logging... The problem is that they might blindly + // add additional output which will break things if what is actually being + // output is anything but a webpage... like an image or video or audio or + // whatever the datastream is here. + $GLOBALS['devel_shutdown'] = FALSE; + + if ($version !== NULL) { + if (isset($datastream[$version])) { + $datastream = $datastream[$version]; + } + else { + return drupal_not_found(); + } + } + header('Content-type: ' . $datastream->mimetype); if ($datastream->controlGroup == 'M' || $datastream->controlGroup == 'X') { header('Content-length: ' . $datastream->size); @@ -41,13 +57,164 @@ function islandora_view_datastream(AbstractDatastream $datastream, $download = F $filename = $datastream->label . '.' . $extension; header("Content-Disposition: attachment; filename=\"$filename\""); } + + $cache_check = islandora_view_datastream_cache_check($datastream); + if ($cache_check !== 200) { + if ($cache_check === 304) { + header('HTTP/1.1 304 Not Modified'); + } + elseif ($cache_check === 412) { + header('HTTP/1.0 412 Precondition Failed'); + } + } + islandora_view_datastream_set_cache_headers($datastream); + drupal_page_is_cacheable(FALSE); // Try not to load the file into PHP memory! - ob_end_flush(); - $datastream->getContent('php://output'); + // Close and flush ALL the output buffers! + while (@ob_end_flush()) { + }; + + // New content needed. + if ($cache_check === 200) { + $datastream->getContent('php://output'); + } exit(); } +/** + * Parse "etags" from HTTP If-Match or If-None-Match headers. + * + * Parses from the CSV-like struture supported by HTTP headers into an array, + * so `"asdf", "fdsa", W/"2132"` should become an array containing the strings: + * - asdf + * - fdsa + * - 2132 + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 + * + * @param string $header_value + * The value from the headers. + * + * @return array + * An array containing all the etags present. + */ +function islandora_parse_http_match_headers($header_value) { + $matches = array(); + // Match the CSV-like structure supported by the HTTP headers. + $count = preg_match_all('/(((W\/)?("?)(\*|.+?)\4)(, +)?)/', $header_value, $matches); + // The fifth sub-expression/group is which will contain the etags. + return $matches[5]; +} + +/** + * Validate cache headers. + * + * @param AbstractDatastream $datastream + * The datastream for which to check the request headers against. + * + * @return int + * An integer representing the HTTP response code. One of: + * - 200: Proceed as normal. (Full download). + * - 304: Resource hasn't changed; pass cache validation. + * - 412: Resource has changed; fail cache validation. + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + */ +function islandora_view_datastream_cache_check(AbstractDatastream $datastream) { + if (!variable_get('islandora_use_datastream_cache_headers', TRUE)) { + return 200; + } + + // Let's assume that if we get here, we'll be able to complete the request. + $return = 200; + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + $modified_since = DateTime::createFromFormat('D, d M Y H:i:s e', $_SERVER['HTTP_IF_MODIFIED_SINCE']); + if ($datastream->createdDate->getTimestamp() - $modified_since->getTimestamp() > 0) { + // Changed! + return $return; + } + else { + $return = 304; + } + } + if ($return === 200 && isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) { + $unmodified_since = DateTime::createFromFormat('D, d M Y H:i:s e', $_SERVER['HTTP_IF_UNMODIFIED_SINCE']); + if ($datastream->createdDate->getTimestamp() !== $unmodified_since->getTimestamp()) { + // Changed! + $return = 412; + } + else { + return $return; + } + } + + // Only consider Etags we have provided. + if (isset($datastream->checksum)) { + $tags = array(); + foreach ($datastream as $offset => $version) { + if (isset($version->checksum)) { + $tags[$offset] = $version->checksum; + } + } + + if ($return === 200 && isset($_SERVER['HTTP_IF_MATCH'])) { + $request_tags = islandora_parse_http_match_headers($_SERVER['HTTP_IF_MATCH']); + if (in_array('*', $request_tags) || count(array_intersect($tags, $request_tags)) > 0) { + // There's a match... Let things go ahead. + return $return; + } + else { + $return = 412; + } + } + if (in_array($return, array(200, 304), TRUE) && isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + $request_tags = islandora_parse_http_match_headers($_SERVER['HTTP_IF_NONE_MATCH']); + if (in_array('*', $request_tags) || count(array_intersect($tags, $request_tags)) > 0) { + $return = 304; + } + else { + $return = 200; + } + } + } + + return $return; +} + +/** + * Set various HTTP headers for caching. + * + * @param AbstractDatastream $datastream + * The datastream being viewed/downloaded. + */ +function islandora_view_datastream_set_cache_headers(AbstractDatastream $datastream) { + if (variable_get('islandora_use_datastream_cache_headers', TRUE)) { + // Force cache revalidation. + header('Expires: Sun, 19 Nov 1978 05:00:00 GMT'); + $cache_control = array(); + if ($datastream->parent->repository->api->connection->username == 'anonymous') { + $cache_control[] = 'public'; + } + else { + $cache_control[] = 'private'; + } + $cache_control[] = 'must-revalidate'; + $cache_control[] = 'max-age=0'; + header('Cache-Control: ' . implode(', ', $cache_control)); + header('Last-Modified: ' . $datastream->createdDate->format('D, d M Y H:i:s \G\M\T')); + if (isset($datastream->checksum)) { + header("Etag: \"{$datastream->checksum}\""); + } + } + else { + header_remove('Cache-Control'); + header_remove('Expires'); + } +} + /** * Get the human readable size of the given datastream. * @@ -69,12 +236,31 @@ function islandora_datastream_get_human_readable_size(AbstractDatastream $datast * * @param AbstractDatastream $datastream * The datastream to generated the url to. + * @param string $type + * One of: + * - download + * - view + * @param int $version + * (Optional) The version of the datastream to get a URL for. * * @return string * either the 'view' or 'download' url for the given datastream. */ -function islandora_datastream_get_url(AbstractDatastream $datastream, $type = 'download') { - return $datastream->controlGroup == 'R' ? $datastream->url : "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/$type"; +function islandora_datastream_get_url(AbstractDatastream $datastream, $type = 'download', $version = NULL) { + if ($version === NULL) { + $link = "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/$type"; + } + else { + $link = "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/version/$version/$type"; + $datastream = $datastream[$version]; + } + + if ($datastream->controlGroup == 'R') { + return $datastream->url; + } + else { + return $link; + } } /** @@ -82,6 +268,9 @@ function islandora_datastream_get_url(AbstractDatastream $datastream, $type = 'd * * @param AbstractDatastream $datastream * The datastream to generated the url to. + * + * @return string + * Markup containing the link to the confirm form to delete the datastream. */ function islandora_datastream_get_delete_link(AbstractDatastream $datastream) { $message = islandora_deprecated('7.x-1.2', 'Use the "islandora_datastream_delete_link" theme implementation.'); @@ -97,6 +286,9 @@ function islandora_datastream_get_delete_link(AbstractDatastream $datastream) { * * @param AbstractDatastream $datastream * The datastream to generated the url to. + * + * @return string + * Markup containing the link to edit the datastream. */ function islandora_datastream_edit_get_link(AbstractDatastream $datastream) { $message = islandora_deprecated('7.x-1.2', 'Use the "islandora_datastream_edit_link" theme implementation.'); @@ -139,6 +331,9 @@ function islandora_edit_datastream(AbstractDatastream $datastream) { * * @param array $edit_registry * A list of 'islandora_edit_datastream_registry' values. + * + * @return array + * A Drupal renderable array containing the "edit" markup. */ function islandora_edit_datastream_registry_render(array $edit_registry) { $markup = ''; diff --git a/includes/datastream.version.inc b/includes/datastream.version.inc new file mode 100644 index 00000000..8c02aaad --- /dev/null +++ b/includes/datastream.version.inc @@ -0,0 +1,268 @@ +parent; + drupal_set_title(t("@dsid Previous Versions", array('@dsid' => $datastream->id))); + $audit_values = islandora_get_audit_trail($parent->id, $datastream->id); + + $header = array(); + $header[] = array('data' => t('Created Date')); + $header[] = array('data' => t('Size')); + $header[] = array('data' => t('Label')); + $header[] = array('data' => t('Responsibility')); + $header[] = array('data' => t('Mime type')); + $header[] = array('data' => t('Operations'), 'colspan' => '2'); + $rows = array(); + + foreach ($datastream as $version => $datastream_version) { + $row = array(); + $reponsibility = $parent->owner; + foreach ($audit_values as $audit_value) { + $internal = $datastream_version->createdDate; + if ($audit_value['date'] == $datastream_version->createdDate) { + $reponsibility = $audit_value['responsibility']; + } + } + $user = user_load_by_name($reponsibility); + if ($user) { + $user_id = $user->uid; + $user_val = l($reponsibility, "user/$user_id"); + } + else { + $user_val = $reponsibility; + } + $row[] = array( + 'class' => 'datastream-date', + 'data' => theme('islandora_datastream_view_link', array( + 'datastream' => $datastream, + 'label' => $datastream_version->createdDate->format(DATE_RFC850), + 'version' => $version, + )), + ); + $row[] = array( + 'class' => 'datastream-size', + 'data' => islandora_datastream_get_human_readable_size($datastream_version), + ); + $row[] = array( + 'class' => 'datastream-label', + 'data' => $datastream_version->label, + ); + $row[] = array( + 'class' => 'datastream-responsibility', + 'data' => $user_val, + ); + $row[] = array( + 'class' => 'datastream-mime', + 'data' => $datastream_version->mimeType, + ); + $row[] = array( + 'class' => 'datastream-delete', + 'data' => theme('islandora_datastream_delete_link', array( + 'datastream' => $datastream, + 'version' => $version, + )), + ); + $row[] = array( + 'class' => 'datastream-revert', + 'data' => theme('islandora_datastream_revert_link', array( + 'datastream' => $datastream, + 'version' => $version, + )), + ); + $rows[] = $row; + } + + return theme('table', array('header' => $header, 'rows' => $rows)); +} + +/** + * The admin delete datastream form. + * + * @param array $form + * The Drupal form. + * @param array $form_state + * The Drupal form state. + * @param AbstractDatastream $datastream + * The datastream to be deleted. + * @param string $version + * The version number of the datastream we are trying to delete. + * + * @return array + * The drupal form definition. + */ +function islandora_delete_datastream_version_form(array $form, array &$form_state, AbstractDatastream $datastream, $version) { + if (!isset($datastream[$version]) || count($datastream) < 2) { + return drupal_not_found(); + } + + $form_state['datastream'] = $datastream; + $form_state['version'] = $version; + return confirm_form($form, + t('Are you sure you want to delete version @version of the @dsid datastream?', array('@dsid' => $datastream->id, '@version' => $version)), + "islandora/object/{$datastream->parent->id}", + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); +} + +/** + * Submit handler for the delete datastream form. + * + * Purges/Delete's the given AbstractDatastream if possible. + * + * The ISLANDORA_PRE_PURGE_DATASTREAM_HOOK will query other modules as to + * whether the given FedoraDatastream + * should be: blocked from purging; state set to 'Deleted'; or purged. + * + * @param array $form + * The Drupal form. + * @param array $form_state + * The Drupal form state. + */ +function islandora_delete_datastream_version_form_submit(array $form, array &$form_state) { + $datastream = $form_state['datastream']; + $version = $form_state['version']; + + $datastream_id = $datastream->id; + $object = $datastream->parent; + + try { + unset($datastream[$version]); + } + catch (Exception $e) { + drupal_set_message(t('Error deleting version %v of %s datastream from object %o %e', array( + '%v' => $version, + '%s' => $datastream_id, + '%o' => $object->label, + '%e' => $e->getMessage())), 'error'); + } + + drupal_set_message(t('%d datastream version successfully deleted from Islandora object %o', array( + '%d' => $datastream_id, + '%o' => $object->label))); + + $form_state['redirect'] = "islandora/object/{$object->id}/datastream/{$datastream->id}/version"; +} + +/** + * The admin revert datastream form. + * + * @param array $form + * The Drupal form. + * @param array $form_state + * The Drupal form state. + * @param AbstractDatastream $datastream + * The datastream to be deleted. + * @param string $version + * The version number of the datastream we are trying to revert to. + * + * @return array + * The drupal form definition. + */ +function islandora_revert_datastream_version_form(array $form, array &$form_state, AbstractDatastream $datastream, $version) { + if (!isset($datastream[$version]) || count($datastream) < 2) { + return drupal_not_found(); + } + + $form_state['dsid'] = $datastream->id; + $form_state['object_id'] = $datastream->parent->id; + $form_state['version'] = $version; + + return confirm_form($form, + t('Are you sure you want to revert to version @version of the @dsid datastream?', array('@dsid' => $datastream->id, '@version' => $version)), + "islandora/object/{$datastream->parent->id}", + "", + t('Revert'), + t('Cancel') + ); +} + +/** + * Submit handler for the revert datastream form. + * + * Reverts the given AbstractDatastream if possible. + * + * @param array $form + * The Drupal form. + * @param array $form_state + * The Drupal form state. + */ +function islandora_revert_datastream_version_form_submit(array $form, array &$form_state) { + $islandora_object = islandora_object_load($form_state['object_id']); + + $datastream_to_revert = $islandora_object[$form_state['dsid']]; + $version = $form_state['version']; + + // Create file holding specified datastream version, and set datastream to it. + $datastream_to_revert_to = $datastream_to_revert[$version]; + if (in_array($datastream_to_revert->controlGroup, array('R', 'E'))) { + $datastream_to_revert->url = $datastream_to_revert_to->url; + } + else { + $filename = file_create_filename('datastream_temp_file', 'temporary://'); + $datastream_to_revert_to->getContent($filename); + $datastream_to_revert->setContentFromFile($filename); + file_unmanaged_delete($filename); + } + + if ($datastream_to_revert->mimeType != $datastream_to_revert_to->mimeType) { + $datastream_to_revert->mimeType = $datastream_to_revert_to->mimeType; + } + if ($datastream_to_revert->label != $datastream_to_revert_to->label) { + $datastream_to_revert->label = $datastream_to_revert_to->label; + } + + drupal_set_message(t('%d datastream successfully reverted to version %v for Islandora object %o', array( + '%d' => $datastream_to_revert->id, + '%v' => $version, + '%o' => $islandora_object->label))); + + $form_state['redirect'] = "islandora/object/{$islandora_object->id}/datastream/{$datastream_to_revert->id}/version"; +} + +/** + * Gets Audit datastream values from foxml. + * + * @param String $pid + * PID of parent object + * + * @return array + * Array of audit values + */ +function islandora_get_audit_trail($pid, $dsid) { + $url = variable_get('islandora_base_url', 'http://localhost:8080/fedora'); + $connection = islandora_get_tuque_connection(NULL, $url); + $xml = $connection->api->m->getObjectXml($pid); + $simple_xml = simplexml_load_string($xml); + $fox_ns = "info:fedora/fedora-system:def/foxml#"; + $audit_ns = 'info:fedora/fedora-system:def/audit#'; + $foxml_nodes = $simple_xml->children($fox_ns); + foreach ($foxml_nodes as $node) { + if ($node->attributes()->ID == "AUDIT") { + $content = $node->datastreamVersion->xmlContent; + $audit_nodes = $content->children($audit_ns); + } + } + $audit_values = array(); + if (isset($audit_nodes)) { + foreach ($audit_nodes->auditTrail->record as $record) { + if ($dsid == $record->componentID) { + $values['responsibility'] = $record->responsibility; + $values['date'] = $record->date; + $audit_values[] = $values; + } + } + } + return $audit_values; +} diff --git a/includes/derivatives.inc b/includes/derivatives.inc new file mode 100644 index 00000000..5ba4bb56 --- /dev/null +++ b/includes/derivatives.inc @@ -0,0 +1,123 @@ + FALSE, + ); + $hooks = islandora_invoke_hook_list(ISLANDORA_DERVIATIVE_CREATION_HOOK, $object->models, array($object)); + uasort($hooks, 'drupal_sort_weight'); + $results = array(); + + if (array_key_exists('source_dsid', $options)) { + $hooks = array_filter($hooks, function($filter_hook) use($options) { + return array_key_exists('source_dsid', $filter_hook) && + $filter_hook['source_dsid'] == $options['source_dsid']; + }); + } + + if (array_key_exists('destination_dsid', $options)) { + $hooks = array_filter($hooks, function($filter_hook) use($options) { + return array_key_exists('destination_dsid', $filter_hook) && + $filter_hook['destination_dsid'] == $options['destination_dsid']; + }); + } + + foreach ($hooks as $hook) { + if (isset($hook['file'])) { + require_once $hook['file']; + } + foreach ($hook['function'] as $function) { + if (function_exists($function)) { + $logging = call_user_func($function, $object, $options['force']); + if (!empty($logging)) { + $results[] = $logging; + } + } + else { + watchdog('islandora', 'Unable to call derivative function @function as it was not found!', array('@function' => $function), WATCHDOG_ERROR); + } + } + } + return $results; +} + +/** + * Handles the logging of derivative messages. + * + * @param array $logging_results + * An array of messages describing the outcome of the derivative events. + * Each individual message array has the following structure: + * - success: Bool denoting whether the operation was successful. + * - messages: An array structure containing: + * - message: A string passed through t() describing the + * outcome of the operation. + * - message_sub: (Optional) Substitutions to be passed along to t() or + * watchdog. + * - type: A string denoting whether the output is to be + * drupal_set_messaged (dsm) or watchdogged (watchdog). + * - severity: (Optional) A severity level / status to be used when + * logging messages. Uses the defaults of drupal_set_message and + * watchdog if not defined. + */ +function islandora_derivative_logging(array $logging_results) { + foreach ($logging_results as $result) { + foreach ($result['messages'] as $message) { + if ($message['type'] === 'dsm') { + if (isset($message['severity']) && $message['severity'] != 'status') { + drupal_set_message(filter_xss(format_string($message['message'], isset($message['message_sub']) ? $message['message_sub'] : array())), $message['severity']); + } + else { + if (!isset($_SESSION['islandora_event_messages'])) { + $_SESSION['islandora_event_messages'] = array(); + } + $_SESSION['islandora_event_messages'][] = array( + 'message' => filter_xss(format_string($message['message'], isset($message['message_sub']) ? $message['message_sub'] : array())), + 'severity' => 'status', + ); + drupal_set_message(l(t('Derivatives successfully created.'), 'islandora/event-status'), 'status', FALSE); + } + } + else { + // We know what we are doing here. Passing through the translated + // message and the substitutions needed. We are using + // call_user_func until such time as the @ignore changes + // are merged into the standard release for Coder. + call_user_func('watchdog', 'islandora_derivatives', $message['message'], isset($message['message_sub']) ? $message['message_sub'] : array(), isset($message['severity']) ? $message['severity'] : WATCHDOG_NOTICE); + } + } + } +} diff --git a/includes/dublin_core.inc b/includes/dublin_core.inc index ec058181..ae59ef5c 100644 --- a/includes/dublin_core.inc +++ b/includes/dublin_core.inc @@ -130,8 +130,11 @@ class DublinCore { } $dc_label = explode(':', $field); $element_label = drupal_ucfirst($dc_label[1]); - $dc_array[$field]['label'] = $element_label; - $dc_array[$field]['value'] = $value; + $i18n_object_id = drupal_strtolower($element_label); + $dc_array[$field]['label'] = function_exists('i18n_string') ? + i18n_string("islandora:dc:{$i18n_object_id}:label", $element_label) : + $element_label; + $dc_array[$field]['value'] = filter_xss($value); $dc_array[$field]['class'] = drupal_strtolower(preg_replace('/[^A-Za-z0-9]/', '-', $field)); $dc_array[$field]['dcterms'] = preg_replace('/^dc/', 'dcterms', $field); } @@ -140,7 +143,6 @@ class DublinCore { return $dc_array; } - /** * Creates a new instance of the class by parsing dc_xml. * diff --git a/includes/ingest.form.inc b/includes/ingest.form.inc index 6faa642c..89376f7d 100644 --- a/includes/ingest.form.inc +++ b/includes/ingest.form.inc @@ -57,6 +57,12 @@ function islandora_ingest_form(array $form, array &$form_state, array $configura return islandora_ingest_form_execute_step($form, $form_state); } catch (Exception $e) { + watchdog( + 'islandora', + 'Exception during ingest form processing with Message: "@exception", and Trace: @trace', + array('@exception' => $e->getMessage(), '@trace' => $e->getTraceAsString()), + WATCHDOG_ERROR + ); drupal_set_message($e->getMessage(), 'error'); return array(array( '#markup' => l(t('Back'), 'javascript:window.history.back();', array('external' => TRUE)))); @@ -186,10 +192,10 @@ function islandora_ingest_form_get_step(array &$form_state, $step_id = NULL) { * * @param array $form_state * The Drupal form state. - * @param string $step + * @param array $step * The step relative to the result, if not provided the current step is used. * - * @return string + * @return array|null * The next step if found, NULL otherwise. */ function islandora_ingest_form_get_next_step(array &$form_state, array $step = NULL) { @@ -205,10 +211,10 @@ function islandora_ingest_form_get_next_step(array &$form_state, array $step = N * * @param array $form_state * The Drupal form state. - * @param string $step + * @param array $step * The step relative to the result, if not provided the current step is used. * - * @return string + * @return array|null * The next step if found, NULL otherwise. */ function islandora_ingest_form_get_previous_step(array &$form_state, array $step = NULL) { @@ -329,6 +335,9 @@ function islandora_ingest_form_decrement_step(array &$form_state) { * The Drupal form. * @param array $form_state * The Drupal form state. + * @param string $step_id + * The ID of the step relative to the result, if not provided the current + * step_id is used. * * @return array * The form definition of the current step. @@ -363,6 +372,8 @@ function islandora_ingest_form_execute_step(array $form, array &$form_state, $st * The Drupal form. * @param array $form_state * The Drupal form state. + * @param array $step + * The step we are executing. * * @return array * The form definition of the given step. @@ -391,6 +402,8 @@ function islandora_ingest_form_execute_form_step(array $form, array &$form_state * The Drupal form. * @param array $form_state * The Drupal form state. + * @param array $step + * The step that execution begins from. */ function islandora_ingest_form_execute_consecutive_callback_steps(array $form, array &$form_state, array $step) { do { @@ -409,10 +422,15 @@ function islandora_ingest_form_execute_consecutive_callback_steps(array $form, a * The Drupal form. * @param array $form_state * The Drupal form state. + * @param array $step + * The step currently being executed. */ function islandora_ingest_form_execute_callback_step(array $form, array &$form_state, array $step) { $args = array(&$form_state); $args = isset($step['do_function']['args']) ? array_merge($args, $step['do_function']['args']) : $args; + if (isset($step['do_function']['file'])) { + require_once drupal_get_path('module', $step['module']) . "/" . $step['do_function']['file']; + } call_user_func_array($step['do_function']['function'], $args); } @@ -425,6 +443,8 @@ function islandora_ingest_form_execute_callback_step(array $form, array &$form_s * The Drupal form. * @param array $form_state * The Drupal form state. + * @param array $step + * The step that execution begins from. */ function islandora_ingest_form_undo_consecutive_callback_steps(array $form, array &$form_state, array $step) { do { @@ -443,10 +463,15 @@ function islandora_ingest_form_undo_consecutive_callback_steps(array $form, arra * The Drupal form. * @param array $form_state * The Drupal form state. + * @param array $step + * The step which the undo callback is being called on. */ function islandora_ingest_form_undo_callback_step(array $form, array &$form_state, array $step) { $args = array(&$form_state); $args = isset($step['undo_function']['args']) ? array_merge($args, $step['undo_function']['args']) : $args; + if (isset($step['undo_function']['file'])) { + require_once drupal_get_path('module', $step['module']) . "/" . $step['undo_function']['file']; + } call_user_func_array($step['undo_function']['function'], $args); } @@ -457,11 +482,13 @@ function islandora_ingest_form_undo_callback_step(array $form, array &$form_stat * The Drupal form. * @param array $form_state * The Drupal form state. + * @param array $step + * An array defining a ingest step. * * @return array * The stepified Drupal form definition for the given step. */ -function islandora_ingest_form_stepify(array $form, array &$form_state, $step) { +function islandora_ingest_form_stepify(array $form, array &$form_state, array $step) { $first_form_step = islandora_ingest_form_on_first_form_step($form_state); $last_form_step = islandora_ingest_form_on_last_form_step($form_state); $form['form_step_id'] = array( @@ -682,16 +709,55 @@ function islandora_ingest_form_ingest_button(array &$form_state) { '#type' => 'submit', '#name' => 'ingest', '#value' => t('Ingest'), + '#process' => array('islandora_ingest_form_ingest_button_process'), '#validate' => $validate, '#submit' => $submit, ); } +/** + * Process hook for the ingest button, adds the please wait spinning icon. + */ +function islandora_ingest_form_ingest_button_process(array $element) { + $settings['spinner'][$element['#id']] = array( + 'message' => t('Please be patient while the the page loads.'), + 'options' => array( + 'lines' => 10, + 'length' => 20, + 'width' => 10, + 'radius' => 30, + 'corners' => 1, + 'rotate' => 0, + 'direction' => 1, + 'color' => '#000', + 'speed' => 1, + 'trail' => 60, + 'shadow' => FALSE, + 'hwaccel' => FALSE, + 'className' => 'spinner', + 'zIndex' => 2000000000, + 'top' => 'auto', + 'left' => 'auto', + ), + ); + drupal_add_js($settings, 'setting'); + $islandora_path = drupal_get_path('module', 'islandora'); + $element['#attached'] = array( + 'js' => array( + "$islandora_path/js/spin/spin.min.js", + "$islandora_path/js/spinner.js", + ), + ); + return $element; +} + /** * The submit handler for the ingest form. * * Attempts to ingest every object built by the previous steps. * + * @param array $form + * The Drupal form. * @param array $form_state * The Drupal form state. */ @@ -703,16 +769,34 @@ function islandora_ingest_form_submit(array $form, array &$form_state) { islandora_ingest_form_execute_consecutive_callback_steps($form, $form_state, $step); } // Ingest the objects. + $set_redirect = TRUE; foreach ($form_state['islandora']['objects'] as &$object) { try { islandora_add_object($object); - $form_state['redirect'] = "islandora/object/{$object->id}"; + // We want to redirect to the first object as it's considered to be the + // primary object. + if ($set_redirect) { + $form_state['redirect'] = "islandora/object/{$object->id}"; + $set_redirect = FALSE; + } + drupal_set_message( + t('"@label" (ID: @pid) has been ingested.', array('@label' => $object->label, '@pid' => $object->id)), + 'status'); } catch (Exception $e) { // If post hooks throws it may already exist at this point but may be // invalid, so don't say failed. - watchdog('islandora', 'Exception Message: @exception.', array('@exception' => $e->getMessage()), WATCHDOG_ERROR); - drupal_set_message(t('A problem occured while ingesting "@label" (ID: @pid), please notifiy the administrator.', array('@label' => $object->label, '@pid' => $object->id)), 'error'); + watchdog( + 'islandora', + 'Exception during ingest with Message: "@exception", and Trace: @trace', + array('@exception' => $e->getMessage(), '@trace' => $e->getTraceAsString()), + WATCHDOG_ERROR + ); + drupal_set_message( + t('A problem occured while ingesting "@label" (ID: @pid), please notifiy the administrator.', + array('@label' => $object->label, '@pid' => $object->id)), + 'error' + ); } } // XXX: Foreaching with references can be weird... The reference exists in diff --git a/includes/manage_deleted_objects.inc b/includes/manage_deleted_objects.inc new file mode 100644 index 00000000..2708efe3 --- /dev/null +++ b/includes/manage_deleted_objects.inc @@ -0,0 +1,340 @@ + 'markup', + '#markup' => t("There are no deleted objects in this repository."), + ); + return $form; + } + $form['message'] = array( + '#type' => 'markup', + '#markup' => t("Select content models of deleted objects."), + ); + $form['mapped_contentmodels'] = array( + '#type' => 'hidden', + '#value' => $contentmodels_with_deleted_members, + ); + $table_element = islandora_content_model_select_table_form_element(NULL); + + foreach ($table_element['#options'] as $option) { + if (!in_array($option['pid'], $elegible_contentmodels)) { + unset($table_element['#options'][$option['pid']]); + } + if (array_key_exists($option['pid'], $chosen_contentmodels)) { + $table_element['#default_value'][$option['pid']] = TRUE; + } + } + + $form['contentmodels'] = $table_element; + $form['next'] = array( + '#type' => 'submit', + '#value' => t('Next'), + ); + + return $form; +} + +/** + * Submit handler for deletion management prep form. + * + * @param array $form + * The form. + * @param array $form_state + * The form state. + */ +function islandora_deleted_objects_prep_form_submit($form, $form_state) { + $content_models = $form_state['values']['contentmodels']; + $chosen = function($element) { + return $element; + }; + $serialized_contentmodels = serialize(array_filter($content_models, $chosen)); + drupal_goto("admin/islandora/restore/manage/$serialized_contentmodels"); +} + +/** + * The deletion management prep form. + * + * @param array $form + * The Drupal form definition. + * @param array $form_state + * The Drupal form state. + * + * @return array + * The Drupal form definition. + */ +function islandora_deleted_objects_manage_form($form, $form_state, $serialized_chosen = NULL) { + $form['previous'] = array( + '#type' => 'submit', + '#value' => t('Previous'), + '#attributes' => array('source' => 'previous'), + ); + + $chosen_contentmodels = unserialize($serialized_chosen); + $content_models_with_deleted = islandora_get_contentmodels_with_deleted_members(); + foreach ($chosen_contentmodels as $contentmodel) { + if (!array_key_exists($contentmodel, $content_models_with_deleted)) { + unset($chosen_contentmodels[$contentmodel]); + } + } + + if (empty($chosen_contentmodels)) { + $form['message'] = array( + '#type' => 'markup', + '#markup' => t("There are no deleted objects with the selected content models in this repository."), + ); + return $form; + } + + if (is_array($chosen_contentmodels)) { + foreach ($chosen_contentmodels as $key => $value) { + if (in_array($key, $content_models_with_deleted)) { + $chosen_contentmodels[$key] = $content_models_with_deleted[$key]; + } + } + } + + $tuque = islandora_get_tuque_connection(); + $repository = $tuque->repository; + // Query brings back fedora-system:FedoraObject-3.0, doubling the results. + $total = $repository->ri->countQuery(islandora_get_deleted_query($chosen_contentmodels), 'sparql') / 2; + $limit = 25; + if ($total < 28) { + $limit = $total; + } + $current_page = pager_default_initialize($total, $limit); + $query_limit = $limit * 2; + $offset = $current_page * $query_limit; + $options = islandora_get_deleted_objects($chosen_contentmodels, $query_limit, $offset); + + foreach ($options as &$option) { + $option['content_model'] = $content_models_with_deleted[$option['content_model']]; + } + $form['serialized_chosen'] = array( + '#type' => 'hidden', + '#value' => $serialized_chosen, + ); + $form['pager'] = array( + '#type' => 'markup', + '#markup' => theme('pager', array('quantity', count($options))), + ); + $form['propogate'] = array( + '#title' => t('Apply changes to related objects?'), + '#default_value' => TRUE, + '#description' => t("Objects associated with selected objects will also be purged/restored. ie page objects associated with a book object."), + '#type' => 'checkbox', + ); + $form['chosen'] = array( + '#type' => 'hidden', + '#value' => $chosen_contentmodels, + ); + $form['objects_to_process'] = array( + '#type' => 'tableselect', + '#header' => array( + 'title' => t('Name'), + 'pid' => t('PID'), + 'content_model' => t('Content Model'), + ), + '#multiple' => TRUE, + '#options' => $options, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Restore selected objects'), + '#attributes' => array('source' => 'restore'), + ); + if (user_access(ISLANDORA_PURGE)) { + $form['purge'] = array( + '#type' => 'submit', + '#value' => t('Irrevocably purge selected objects'), + '#attributes' => array('source' => 'purge'), + ); + } + return $form; +} + +/** + * Submit handler for deletion management form. + * + * @param array $form + * The form. + * @param array $form_state + * The form state. + */ +function islandora_deleted_objects_manage_form_submit($form, $form_state) { + module_load_include('inc', 'islandora', 'includes/utilities'); + $serialized_chosen = isset($form_state['values']['serialized_chosen']) ? $form_state['values']['serialized_chosen'] : NULL; + + if (isset($form_state['clicked_button']['#attributes']['source']) && $form_state['clicked_button']['#attributes']['source'] == 'previous') { + drupal_goto("admin/islandora/restore/prep/$serialized_chosen"); + } + if ($form_state['clicked_button']['#attributes']['source'] == 'restore') { + $descriptor = "Restoring"; + $action = 'islandora_restore_object_by_pid'; + } + if ($form_state['clicked_button']['#attributes']['source'] == 'purge') { + $descriptor = "Purging"; + $action = 'islandora_purge_object_by_pid'; + } + $objects_to_process = array_filter($form_state['values']['objects_to_process']); + $pids_to_restore = $objects_to_process; + if ($form_state['values']['propogate']) { + foreach ($objects_to_process as $pid) { + $fedora_object = islandora_object_load($pid); + $temp = islandora_invoke_hook_list(ISLANDORA_UPDATE_RELATED_OBJECTS_PROPERTIES_HOOK, $fedora_object->models, array($fedora_object)); + if (!empty($temp)) { + $pids_to_restore = array_merge_recursive($pids_to_restore, $temp); + } + } + } + $batch = array( + 'title' => t('@descriptor selected objects', array('@descriptor' => $descriptor)), + 'file' => drupal_get_path('module', 'islandora') . '/includes/manage_deleted_objects.inc', + 'operations' => array(), + ); + + foreach ($pids_to_restore as $pid) { + $batch['operations'][] = array( + $action, + array($pid), + ); + } + batch_set($batch); + batch_process("admin/islandora/restore/manage/$serialized_chosen"); +} + +/** + * Gets PIDS of all deleted objects. + * + * @return array + * PIDS of deleted objects + */ +function islandora_get_deleted_objects($content_models, $limit, $offset) { + $tuque = islandora_get_tuque_connection(); + $repository = $tuque->repository; + $query = islandora_get_deleted_query($content_models, $offset); + $objects = $repository->ri->sparqlQuery($query, $limit); + $deleted_objects = array(); + foreach ($objects as $object) { + if ($object['object']['value'] != "fedora-system:FedoraObject-3.0") { + $pid = $object['subject']['value']; + $cm_pid = $object['object']['value']; + $title = $object['label']['value']; + $deleted_objects[$pid] = array( + 'title' => $title, 'pid' => $pid, + 'content_model' => $content_models[$cm_pid], + ); + } + } + return $deleted_objects; +} + +/** + * Gets PIDS of all content models associated with deleted objects. + * + * @return array + * array of content model pids + */ +function islandora_get_contentmodels_with_deleted_members() { + $tuque = new IslandoraTuque(); + $repository = $tuque->repository; + $query = "PREFIX fm: + SELECT DISTINCT ?object ?label FROM <#ri> + WHERE { + {?subject fm:state fm:Deleted; + fm:hasModel ?object; + } + OPTIONAL{ + ?object fm:label ?label + } + }"; + + $objects = $repository->ri->sparqlQuery($query, -1); + $content_models = array(); + foreach ($objects as $object) { + if ($object['object']['value'] != "fedora-system:FedoraObject-3.0") { + $content_models[$object['object']['value']] = $object['label']['value']; + } + } + return $content_models; +} + +/** + * Restores deleted object. + * + * @param String $pid + * PID of object to be restored + */ +function islandora_restore_object_by_pid($pid) { + $fedora_object = islandora_object_load($pid); + $fedora_object->state = 'A'; +} + +/** + * Purges deleted object. + * + * @param String $pid + * PID of object to be restored + */ +function islandora_purge_object_by_pid($pid) { + $fedora_object = islandora_object_load($pid); + $fedora_object->repository->purgeObject($pid); +} + +/** + * Get query to find all deleted objects by content type. + * + * @param array $content_models + * Content models to restrict search + * @param int $offset + * offset to be added to search + * + * @return String + * Sparql query + */ +function islandora_get_deleted_query($content_models, $offset = 0) { + $candidates = array_keys($content_models); + $first_contentmodel = array_shift($candidates); + $prefix = "PREFIX fm: <" . FEDORA_MODEL_URI . "> "; + $select = "SELECT DISTINCT ?subject ?label ?object FROM <#ri> WHERE { "; + $where_clause = "{?subject fm:hasModel ; + fm:state fm:Deleted; + fm:hasModel ?object; + }"; + $suffix = "} ORDER BY ?subject OFFSET $offset"; + $unions = ''; + foreach ($candidates as $contentmodel) { + $unions .= "UNION {?subject fm:hasModel ; + fm:state fm:Deleted; + fm:hasModel ?object; + } + "; + } + $optional = "OPTIONAL{?subject fm:label ?label}"; + return "$prefix $select $where_clause $unions $optional $suffix"; +} diff --git a/includes/metadata.inc b/includes/metadata.inc new file mode 100644 index 00000000..bf59b830 --- /dev/null +++ b/includes/metadata.inc @@ -0,0 +1,171 @@ + t('None'), + 'description' => t("Don't show any metadata for displaying"), + ); + $viewers = array_merge_recursive($no_viewer, $defined_displays); + + $form['viewers'] = array( + '#type' => 'item', + '#title' => t('Select a viewer'), + '#description' => t('Preferred metadata display for Islandora. These may be provided by third-party modules.'), + '#tree' => TRUE, + '#theme' => 'islandora_viewers_table', + ); + + foreach ($viewers as $name => $profile) { + $options[$name] = ''; + $form['viewers']['name'][$name] = array( + '#type' => 'hidden', + '#value' => $name, + ); + $form['viewers']['label'][$name] = array( + '#type' => 'item', + '#markup' => $profile['label'], + ); + $form['viewers']['description'][$name] = array( + '#type' => 'item', + '#markup' => $profile['description'], + ); + $form['viewers']['configuration'][$name] = array( + '#type' => 'item', + '#markup' => (isset($profile['configuration']) AND $profile['configuration'] != '') ? l(t('configure'), $profile['configuration']) : '', + ); + } + $form['viewers']['default'] = array( + '#type' => 'radios', + '#options' => isset($options) ? $options : array(), + '#default_value' => variable_get('islandora_metadata_display', 'dublin_core'), + ); + } + else { + $form['viewers']['no_viewers'] = array( + '#markup' => t('No viewers detected.'), + ); + } + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + return $form; +} + +/** + * Submit handler for the metadata display form which sets the default viewer. + * + * @param array $form + * An array representing a Drupal form. + * @param array $form_state + * An array containing the Drupal form state. + */ +function islandora_metadata_display_form_submit($form, $form_state) { + variable_set('islandora_metadata_display', $form_state['values']['viewers']['default']); + drupal_set_message(t('The configuration options have been saved.')); +} + +/** + * Metadata display callback for rendering Dublin Core metadata. + * + * @param AbstractObject $object + * An AbstractObject representing an object within Fedora. + * @param bool $print + * Whether the display is being printed or not. + * + * @return string + * Markup representing the rendered metadata from Dublin Core. + */ +function islandora_metadata_display_callback(AbstractObject $object, $print = FALSE) { + $elements = array( + 'islandora_object' => $object, + 'print' => $print, + ); + return theme('islandora_dublin_core_display', $elements); +} + +/** + * Metadata description callback for rendering Dublin Core description. + * + * @param AbstractObject $islandora_object + * An AbstractObject representing an object within Fedora. + * + * @return string + * Markup representing the rendered metadata from Dublin Core. + */ +function islandora_metadata_description_callback(AbstractObject $islandora_object) { + $elements = array( + 'islandora_object' => $islandora_object, + ); + return theme('islandora_dublin_core_description', $elements); +} diff --git a/includes/mime_detect.inc b/includes/mime_detect.inc index 13b66cc8..e76975d6 100644 --- a/includes/mime_detect.inc +++ b/includes/mime_detect.inc @@ -162,6 +162,7 @@ class MimeDetect { "xpm" => "image/x-xpixmap", "xwd" => "image/x-windowdump", // videos: + "mkv" => "video/x-matroska", "mpeg" => "video/mpeg", "mpe" => "video/mpeg", "mpg" => "video/mpeg", @@ -382,4 +383,19 @@ class MimeDetect { return $this->protectedMimeTypes; } + /** + * Get all valid extensions for this MIME type. + * + * @param string $mimetype + * The MIME type we are searching for. + * + * @return array + * An array of valid extensions for this MIME type. + */ + public function getValidExtensions($mimetype) { + $filter = function ($mime) use ($mimetype) { + return $mime == $mimetype; + }; + return array_keys(array_filter($this->protectedMimeTypes, $filter)); + } } diff --git a/includes/object.entity_controller.inc b/includes/object.entity_controller.inc index d3fd6986..9464d7b5 100644 --- a/includes/object.entity_controller.inc +++ b/includes/object.entity_controller.inc @@ -24,6 +24,9 @@ class IslandoraObjectEntityController implements DrupalEntityControllerInterface * The ID's of the entities. * @param array $conditions * The conditions to apply. + * + * @return array + * An array of loaded objects. */ public function load($ids = array(), $conditions = array()) { if (!empty($conditions)) { diff --git a/includes/object_properties.form.inc b/includes/object_properties.form.inc index 44d016b7..676f0d0c 100644 --- a/includes/object_properties.form.inc +++ b/includes/object_properties.form.inc @@ -21,6 +21,11 @@ function islandora_object_properties_form(array $form, array &$form_state, AbstractObject $object) { drupal_set_title($object->label); $form_state['object'] = $object; + $temp = islandora_invoke_hook_list(ISLANDORA_UPDATE_RELATED_OBJECTS_PROPERTIES_HOOK, $object->models, array($object)); + $related_objects_pids = array(); + if (!empty($temp)) { + $related_objects_pids = array_merge_recursive($related_objects_pids, $temp); + } return array( 'pid' => array( '#type' => 'hidden', @@ -54,14 +59,28 @@ function islandora_object_properties_form(array $form, array &$form_state, Abstr '#type' => 'select', '#options' => array('A' => 'Active', 'I' => 'Inactive', 'D' => 'Deleted'), ), + 'propogate' => array( + '#title' => t('Apply changes to related objects?'), + '#default_value' => TRUE, + '#description' => t("Changes to owner and state will applied to associated objects. ie page objects associated with a book object."), + '#type' => 'checkbox', + '#access' => count($related_objects_pids), + ), + 'related_pids' => array( + '#value' => $related_objects_pids, + '#type' => 'hidden', + '#access' => count($related_objects_pids), + ), 'submit' => array( '#type' => 'submit', '#value' => 'Update Properties', ), 'delete' => array( '#type' => 'submit', - '#access' => islandora_object_access(FEDORA_PURGE, $object), - '#value' => t('Delete'), + '#access' => islandora_object_access(ISLANDORA_PURGE, $object), + '#value' => t("Permanently remove '@label' from repository", array( + '@label' => truncate_utf8($object->label, 32, TRUE, TRUE)) + ), '#submit' => array('islandora_object_properties_form_delete'), '#limit_validation_errors' => array(array('pid')), ), @@ -81,32 +100,54 @@ function islandora_object_properties_form_submit(array $form, array &$form_state $owner = $form_state['values']['object_owner']; $state = $form_state['values']['object_state']; $label = $form_state['values']['object_label']; + $propogate = $form_state['values']['propogate']; + $update_owners = FALSE; + $update_states = FALSE; if (isset($owner) && $owner != $object->owner) { try { $object->owner = $owner; + $update_owners = TRUE; drupal_set_message(t('Successfully updated owner %s', array('%s' => $owner))); } catch (Exception $e) { form_set_error('object_owner', t('Error updating owner %s', array('%s' => $e->getMessage()))); } } + + if (isset($label) && $label != $object->label) { + try { + $object->label = $label; + drupal_set_message(t('Successfully updated label %s', array('%s' => check_plain($label)))); + } + catch (Exception $e) { + form_set_error(t('Error updating label %s', array('%s' => $e->getMessage()))); + } + } if (isset($state) && $state != $object->state) { try { $object->state = $state; + $update_states = TRUE; drupal_set_message(t('Successfully updated state %s', array('%s' => $state))); } catch (Exception $e) { form_set_error('object_state', t('Error updating state %s', array('%s' => $e->getMessage()))); } } - if (isset($label) && $label != $object->label) { - try { - $object->label = $label; - drupal_set_message(t('Successfully updated label %s', array('%s' => check_plain($label)))); - } - catch (Exception $e) { - form_set_error(t('Error updating label %s', array('%s' => $e->getMessage()))); + if ($propogate && ($update_states || $update_owners)) { + $related_objects_pids = $form_state['values']['related_pids']; + $batch = array( + 'title' => t('Updating related objects'), + 'file' => drupal_get_path('module', 'islandora') . '/includes/object_properties.form.inc', + 'operations' => array(), + ); + + foreach ($related_objects_pids as $pid) { + $batch['operations'][] = array( + 'islandora_update_object_properties', + array($pid, $update_states, $state, $update_owners, $owner), + ); } + batch_set($batch); } } @@ -121,3 +162,29 @@ function islandora_object_properties_form_submit(array $form, array &$form_state function islandora_object_properties_form_delete(array $form, array &$form_state) { drupal_goto("islandora/object/{$form_state['values']['pid']}/delete"); } + +/** + * Updates object state. + * + * @param string $pid + * PID of object to be updated + * @param bool $update_states + * If TRUE, update object state + * @param string $state + * Desired object state + * @param bool $update_owners + * If TRUE, update Owner + * @param string $owner + * New Owner + */ +function islandora_update_object_properties($pid, $update_states, $state, $update_owners, $owner) { + $fedora_object = islandora_object_load($pid); + if ($fedora_object) { + if ($update_states) { + $fedora_object->state = $state; + } + if ($update_owners) { + $fedora_object->owner = $owner; + } + } +} diff --git a/includes/solution_packs.inc b/includes/solution_packs.inc index e1cf9493..6a005a18 100644 --- a/includes/solution_packs.inc +++ b/includes/solution_packs.inc @@ -28,7 +28,16 @@ function islandora_solution_packs_get_required_objects($module = NULL) { if (!$required_objects) { $connection = islandora_get_tuque_connection(); - $required_objects = module_invoke_all('islandora_required_objects', $connection); + if (isset($module)) { + // The module may be disabled when this function runs, as modules must be + // disabled before they can be uninstalled. We must manually load the + // module file to use it's islandora_required_objects hook. + module_load_include('module', $module, $module); + $required_objects = module_invoke($module, 'islandora_required_objects', $connection); + } + else { + $required_objects = module_invoke_all('islandora_required_objects', $connection); + } } if ($module !== NULL) { @@ -173,7 +182,7 @@ function islandora_solution_pack_form(array $form, array &$form_state, $solution '#markup' => t('Object status: !image !status', array( '!image' => $solution_pack_status_info['image'], '!status' => $solution_pack_status_info['solution_pack'], - )), + )), '#prefix' => '
', '#suffix' => '
', ), @@ -720,7 +729,7 @@ function theme_islandora_viewers_table($variables) { 'header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'islandora-viewers-table'), - )); + )); $output .= drupal_render_children($form); return $output; } @@ -745,7 +754,9 @@ function islandora_get_viewer($params = NULL, $variable_id = NULL, $fedora_objec $viewer_id = islandora_get_viewer_id($variable_id); if ($viewer_id AND $params !== NULL) { $callback = islandora_get_viewer_callback($viewer_id); - return $callback($params, $fedora_object); + if (function_exists($callback)) { + return $callback($params, $fedora_object); + } } } return FALSE; diff --git a/includes/tuque.inc b/includes/tuque.inc index ec278192..7aed2556 100644 --- a/includes/tuque.inc +++ b/includes/tuque.inc @@ -85,6 +85,7 @@ class IslandoraTuque { } if (self::exists()) { + module_load_include('inc', 'islandora', 'includes/tuque_wrapper'); $this->connection = new IslandoraRepositoryConnection($url, $user_string, $pass_string); $this->connection->reuseConnection = TRUE; $this->api = new IslandoraFedoraApi($this->connection); diff --git a/includes/tuque_wrapper.inc b/includes/tuque_wrapper.inc index 96f9919c..98e9c62c 100644 --- a/includes/tuque_wrapper.inc +++ b/includes/tuque_wrapper.inc @@ -8,7 +8,11 @@ * @todo Overload functions and apply pre/post hooks. */ -$islandora_module_path = drupal_get_path('module', 'islandora'); +// This function may not exist when a batch operation is running from a +// multistep form. +if (function_exists('drupal_get_path')) { + $islandora_module_path = drupal_get_path('module', 'islandora'); +} // @todo this until we expost these in a module or library @include_once 'sites/all/libraries/tuque/Datastream.php'; @@ -207,6 +211,7 @@ class IslandoraFedoraApiM extends FedoraApiM { 'params' => $params, ); islandora_alter_datastream($object, $datastream, $context); + $params = $context['params']; if (isset($params['lastModifiedDate'])) { $params['lastModifiedDate'] = (string) $object[$dsid]->createdDate; } @@ -244,6 +249,7 @@ class IslandoraFedoraApiM extends FedoraApiM { 'params' => $params, ); islandora_alter_object($object, $context); + $params = $context['params']; try { if ($context['block']) { throw new Exception('Modify Object was blocked.'); diff --git a/includes/utilities.inc b/includes/utilities.inc index 5ce2ca14..d858ff0c 100644 --- a/includes/utilities.inc +++ b/includes/utilities.inc @@ -14,6 +14,8 @@ * * @param int $bytes * Size in bytes to convert + * @param int $precision + * The amount of decimal precision to show. * * @return string * Human readable size. @@ -389,10 +391,10 @@ function islandora_get_datastreams_requirements_from_models(array $models) { * - "optional": A boolean indicating if the given stream is optional. */ function islandora_get_datastreams_requirements_from_content_model(AbstractObject $object) { - if (empty($object[DS_COMP_STREAM]) || !islandora_datastream_access(FEDORA_VIEW_OBJECTS, $object[DS_COMP_STREAM])) { + if (empty($object[ISLANDORA_DS_COMP_STREAM]) || !islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $object[ISLANDORA_DS_COMP_STREAM])) { return array(); } - $xml = new SimpleXMLElement($object[DS_COMP_STREAM]->content); + $xml = new SimpleXMLElement($object[ISLANDORA_DS_COMP_STREAM]->content); foreach ($xml->dsTypeModel as $ds) { $dsid = (string) $ds['ID']; $optional = strtolower((string) $ds['optional']); @@ -887,9 +889,7 @@ function islandora_as_renderable_array(&$markup_array) { if (!is_array($value)) { // Not a renderable array, just a string. Let's convert it to a // renderable '#markup' element. - $value = array( - '#markup' => $value, - ); + $value = array('#markup' => $value); } elseif (!isset($value['#type']) && !isset($value['#markup'])) { // A simple array--possibly the result of a recursive merge? Let's @@ -900,9 +900,7 @@ function islandora_as_renderable_array(&$markup_array) { // If it is an array at this level, we can assume that it is a // renderable array. If it is not an array, convert to a renderable // '#markup' element. - $inner = array( - '#markup' => $inner, - ); + $inner = array('#markup' => $inner); } } unset($inner); @@ -910,3 +908,76 @@ function islandora_as_renderable_array(&$markup_array) { } unset($value); } + +/** + * Sanitizes an input string to be valid XML. + * + * @param string $input + * An input string. + * @param string $replacement + * What we are replacing invalid characters with, defaults to ''. + * + * @return string + * The sanitized string. + */ +function islandora_sanitize_input_for_valid_xml($input, $replacement = '') { + $input = preg_replace('/[^\x9\xA\xD\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/u', $replacement, $input); + return $input; +} + +/** + * Page callback for session status messages to be sent to drupal_set_message(). + * + * @return array + * Render array containing markup. + */ +function islandora_event_status() { + $results = FALSE; + if (isset($_SESSION['islandora_event_messages'])) { + foreach ($_SESSION['islandora_event_messages'] as $message) { + drupal_set_message($message['message'], $message['severity']); + $results = TRUE; + } + unset($_SESSION['islandora_event_messages']); + } + $text = ($results) ? t('The status messages above will be deleted after viewing this page.') : t('No messages to display.'); + return array('#markup' => $text); +} + +/** + * Scales the given image. + * + * @param object $file + * The image file to scale. + * @param int $width + * The width to scale the derived image to. + * @param int $height + * The height to scale the derived image to. + * + * @return bool + * TRUE if successful, FALSE otherwise. + */ +function islandora_scale_thumbnail($file, $width, $height) { + $real_path = drupal_realpath($file->uri); + $image = image_load($real_path); + try { + if (!empty($image)) { + $scale = image_scale($image, $width, $height, TRUE); + if ($scale) { + return image_save($image); + } + } + } + catch (exception $e) { + drupal_set_message(t( + "Islandora failed to scale image with message: '@message'", + array("@message" => $e->getMessage()))); + watchdog( + 'islandora', + 'Islandora failed to scale image.
With stack: @trace', + array('@trace' => $e->getTraceAsString()), + WATCHDOG_ERROR + ); + } + return FALSE; +} diff --git a/islandora.api.php b/islandora.api.php index 129787f4..04d8bd49 100644 --- a/islandora.api.php +++ b/islandora.api.php @@ -21,8 +21,30 @@ * An array whose values are markup. */ function hook_islandora_view_object($object, $user, $page_number, $page_size) { -} + $output = array(); + if (in_array('islandora:sp_basic_image', $object->models)) { + $resource_url = url("islandora/object/{$object->id}/datastream/OBJ/view"); + $params = array( + 'title' => $object->label, + 'path' => $resource_url, + ); + + // Theme the image seperatly. + $variables['islandora_img'] = theme('image', $params); + $output = theme('islandora_default_print', array( + 'islandora_content' => $variables['islandora_img'])); + } + return $output; +} +/** + * Generate a print friendly page for the given object. + * + * @param object $object + * The object form to print. + */ +function hook_islandora_view_print_object($object) { +} /** * Generate an object's display for the given content model. * @@ -64,7 +86,7 @@ function hook_CMODEL_PID_islandora_view_object_alter(&$object, &$rendered) { } /** - * Generate an object's management display. + * Generate an object's datastreams management display. * * @param AbstractObject $object * A Tuque FedoraObject @@ -76,7 +98,7 @@ function hook_islandora_edit_object($object) { } /** - * Generate an object's management display for the given content model. + * Generate an object's datastreams management display based on content model. * * Content models PIDs have colons and hyphens changed to underscores, to * create the hook name. @@ -91,7 +113,7 @@ function hook_CMODEL_PID_islandora_edit_object($object) { } /** - * Allow management display output to be altered. + * Allow datastreams management display output to be altered. * * @param AbstractObject $object * A Tuque FedoraObject @@ -372,8 +394,17 @@ function hook_islandora_viewer_info() { /** * Returns a list of datastreams that are determined to be undeletable. + * + * The list is used to prevent delete links from being shown. + * + * @param array $models + * An array of content models for the current object. + * + * @return array + * An array of DSIDs that shouldn't be deleted. */ function hook_islandora_undeletable_datastreams(array $models) { + return array('DC', 'MODS'); } /** @@ -396,17 +427,21 @@ function hook_islandora_undeletable_datastreams(array $models) { * - do_function: An associate array including: * - 'function': The callback function to be called. * - 'args': An array of arguments to pass to the callback function. + * - 'file': A file to include (relative to the module's path, including + * the file's extension). * - undo_function: An associate array including: * - 'function': The callback function to be called to reverse the * executed action in the ingest steps. * - 'args': An array of arguments to pass to the callback function. + * - 'file': A file to include (relative to the module's path, including + * the file's extension). * Shared parameters between both types: * - weight: The "weight" of this step--heavier(/"larger") values sink to the * end of the process while smaller(/"lighter") values are executed first. * Both types may optionally include: * - module: A module from which we want to load an include. * "Form" type may optionally include: - * - file: A file to include (relative to the module's path, including the + * - 'file': A file to include (relative to the module's path, including the * file's extension). */ function hook_islandora_ingest_steps(array $form_state) { @@ -455,10 +490,11 @@ function hook_CMODEL_PID_islandora_ingest_steps(array $form_state) { * @param object $user * A loaded user object, as the global $user variable might contain. * - * @return bool|NULL + * @return bool|NULL|array * Either boolean TRUE or FALSE to explicitly allow or deny the operation on * the given object, or NULL to indicate that we are making no assertion - * about the outcome. + * about the outcome. Can also be an array containing multiple + * TRUE/FALSE/NULLs, due to how hooks work. */ function hook_islandora_object_access($op, $object, $user) { switch ($op) { @@ -493,10 +529,11 @@ function hook_CMODEL_PID_islandora_object_access($op, $object, $user) { * @param object $user * A loaded user object, as the global $user variable might contain. * - * @return bool|NULL + * @return bool|NULL|array * Either boolean TRUE or FALSE to explicitly allow or deny the operation on * the given object, or NULL to indicate that we are making no assertion - * about the outcome. + * about the outcome. Can also be an array containing multiple + * TRUE/FALSE/NULLs, due to how hooks work. */ function hook_islandora_datastream_access($op, $object, $user) { switch ($op) { @@ -519,3 +556,175 @@ function hook_islandora_datastream_access($op, $object, $user) { */ function hook_CMODEL_PID_islandora_datastream_access($op, $object, $user) { } + +/** + * Lets one add to the overview tab in object management. + */ +function hook_islandora_overview_object(AbstractObject $object) { + return drupal_render(drupal_get_form('some_form', $object)); +} + +/** + * Lets one add to the overview tab in object management. + * + * Content model specific. + */ +function hook_CMODEL_PID_islandora_overview_object(AbstractObject $object) { + return drupal_render(drupal_get_form('some_form', $object)); +} +/** + * Lets one alter the overview tab in object management. + */ +function hook_islandora_overview_object_alter(AbstractObject &$object, &$output) { + $output = $output . drupal_render(drupal_get_form('some_form', $object)); +} + +/** + * Lets one alter the overview tab in object management. + * + * Content model specific. + */ +function hook_CMODEL_PID_islandora_overview_object_alter(AbstractObject &$object, &$output) { + $output = $output . drupal_render(drupal_get_form('some_form', $object)); +} + +/* + * Defines derivative functions to be executed based on certain conditions. + * + * @param AbstractObject $object + * Object to which derivatives will be added + * This hook fires when an object/datastream is ingested or a datastream is + * modified. + * + * @return array + * An array containing an entry for each derivative to be created. Each entry + * is an array of parameters containing: + * - force: Bool denoting whether we are forcing the generation of + * derivatives. + * - source_dsid: (Optional) String of the datastream id we are generating + * from or NULL if it's the object itself. + * - destination_dsid: (Optional) String of the datastream id that is being + * created. To be used in the UI. + * - weight: A string denoting the weight of the function. This value is + * sorted upon to run functions in order. + * - function: An array of function(s) to be ran when constructing + * derivatives. Functions that are defined to be called for derivation + * creation must have the following structure: + * module_name_derivative_creation_function($object, $force = FALSE) + * These functions must return an array in the structure of: + * - success: Bool denoting whether the operation was successful. + * - messages: An array structure containing: + * - message: A string passed through t() describing the + * outcome of the operation. + * - message_sub: (Optional) Substitutions to be passed along to t() or + * watchdog. + * - type: A string denoting whether the output is to be + * drupal_set_messaged (dsm) or watchdogged (watchdog). + * - severity: (Optional) A severity level / status to be used when + * logging messages. Uses the defaults of drupal_set_message and + * watchdog if not defined. + * - file: A string denoting the path to the file where the function + * is being called from. + */ +function hook_islandora_derivative(AbstractObject $object) { + + $derivatives[] = array( + 'source_dsid' => 'OBJ', + 'destination_dsid' => 'DERIV', + 'weight' => '0', + 'function' => array( + 'islandora_derivatives_test_create_deriv_datastream', + ), + ); + // Test object before adding this derivative. + if ($object['SOMEWEIRDDATASTREAM']->mimetype == "SOMETHING/ODD") { + $derivatives[] = array( + 'source_dsid' => 'SOMEWEIRDDATASTREAM', + 'destination_dsid' => 'STANLEY', + 'weight' => '-1', + 'function' => array( + 'islandora_derivatives_test_create_some_weird_datastream', + ), + ); + } + + $derivatives[] = array( + 'source_dsid' => NULL, + 'destination_dsid' => 'NOSOURCE', + 'weight' => '-3', + 'function' => array( + 'islandora_derivatives_test_create_nosource_datastream', + ), + ); + + return $derivatives; +} + +/** + * Content model specific version of hook_islandora_derivative(). + * + * @see hook_islandora_derivative() + */ +function hook_CMODEL_PID_islandora_derivative() { + +} + +/** + + * Retrieves PIDS of related objects for property updating. + * + * @param AbstractObject $object + * AbstractObject representing deleted object + */ +function hook_islandora_update_related_objects_properties(AbstractObject $object) { + $related_objects = get_all_children_siblings_and_friends($object); + $pids_to_return = array(); + foreach($related_objects as $related_object) { + $pids_to_return[] = $related_object->id; + } + return $pids_to_return; +} +/** + * Alters breadcrumbs used on Solr search results and within Islandora views. + * + * @param array $breadcrumbs + * Breadcrumbs array to be altered by reference. Each element is markup. + * @param string $context + * Where the alter is originating from for distinguishing. + */ +function hook_islandora_breadcrumbs_alter(&$breadcrumbs, $context) { + +} + +/** + * Registry hook for metadata display viewers. + * + * Modules can use this hook to override the default Dublin Core display. + * This hook lets Islandora know which viewers there are available. + * + * @return array + * An associative array where the values are the following: + * -label: Human readable display label for selection. + * -description: A description of what the metadata display viewer does. + * -metadata callback: A callable function that provides the markup to be + * passed off to the template files. Returns markup or FALSE if the viewer + * wishes to default back to the Dublin Core display for the current object. + * -description callback: A callable function that provides the markup to be + * passed for the description. Returns markup or FALSE if the viewer + * wishes to default back to the Dublin Core display for the current object. + * -configuration (Optional): A path to the administration page for the + * metadata display. + + * @see islandora_retrieve_metadata_markup() + */ +function hook_islandora_metadata_display_info() { + return array( + 'hookable_displays_yay' => array( + 'label' => t('Hookable display yay!'), + 'description' => t('This is purely an example of how to implement this.'), + 'metadata callback' => 'hookable_displays_some_function_that_returns_metadata_markup', + 'description callback' => 'hookable_displays_some_function_that_returns_description_markup', + 'configuration' => 'admin/hookable_displays_yay/somepath', + ), + ); +} diff --git a/islandora.drush.inc b/islandora.drush.inc index b2e75c47..6ba73cb5 100644 --- a/islandora.drush.inc +++ b/islandora.drush.inc @@ -26,6 +26,10 @@ function islandora_drush_command() { 'drupal dependencies' => array( 'islandora', ), + 'examples' => array( + 'drush -u 1 ispiro --module=islandora' => dt('Install missing solution pack objects for the "islandora" module.'), + 'drush -u 1 ispiro --module=islandora --force' => dt('Install all solution pack objects for the "islandora" module, purging any which currently exist.'), + ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, ); $commands['islandora-solution-pack-uninstall-required-objects'] = array( @@ -36,13 +40,17 @@ function islandora_drush_command() { 'required' => TRUE, ), 'force' => array( - 'description' => dt('Force reinstallation of the objects.'), + 'description' => dt('Force uninstallation of the objects.'), ), ), 'aliases' => array('ispuro'), 'drupal dependencies' => array( 'islandora', ), + 'examples' => array( + 'drush -u 1 ispuro --module=islandora' => dt('Uninstall solution pack objects for the "islandora" module.'), + 'drush -u 1 ispuro --module=islandora --force' => dt('Force uninstallation of all solution pack objects for the "islandora" module.'), + ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, ); $commands['islandora-solution-pack-required-objects-status'] = array( @@ -56,6 +64,10 @@ function islandora_drush_command() { 'drupal dependencies' => array( 'islandora', ), + 'examples' => array( + 'drush -u 1 ispros' => dt('Get the status of all solution pack objects.'), + 'drush -u 1 ispros --module=islandora' => dt('Get the status of solution pack objects for the "islandora" module.'), + ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, ); diff --git a/islandora.info b/islandora.info index ce6ba947..732143b5 100644 --- a/islandora.info +++ b/islandora.info @@ -1,6 +1,8 @@ name = Islandora description = "View and manage Fedora objects" package = Islandora +dependencies[] = image +dependencies[] = file version = 7.x-dev core = 7.x configure = admin/islandora/configure @@ -17,4 +19,7 @@ files[] = tests/hooks.test files[] = tests/ingest.test files[] = tests/hooked_access.test files[] = tests/islandora_manage_permissions.test +files[] = tests/datastream_versions.test +files[] = tests/datastream_cache.test +files[] = tests/derivatives.test php = 5.3 diff --git a/islandora.module b/islandora.module index 5b45fb1a..25244bbc 100644 --- a/islandora.module +++ b/islandora.module @@ -24,24 +24,30 @@ */ // Common datastreams. -define('DS_COMP_STREAM', 'DS-COMPOSITE-MODEL'); +define('ISLANDORA_DS_COMP_STREAM', 'DS-COMPOSITE-MODEL'); // Permissions. -define('FEDORA_VIEW_OBJECTS', 'view fedora repository objects'); -define('FEDORA_METADATA_EDIT', 'edit fedora metadata'); -define('FEDORA_ADD_DS', 'add fedora datastreams'); -define('FEDORA_INGEST', 'ingest fedora objects'); -define('FEDORA_PURGE', 'delete fedora objects and datastreams'); -define('FEDORA_MANAGE_PROPERTIES', 'manage object properties'); +define('ISLANDORA_VIEW_OBJECTS', 'view fedora repository objects'); +define('ISLANDORA_METADATA_EDIT', 'edit fedora metadata'); +define('ISLANDORA_ADD_DS', 'add fedora datastreams'); +define('ISLANDORA_INGEST', 'ingest fedora objects'); +define('ISLANDORA_PURGE', 'delete fedora objects and datastreams'); +define('ISLANDORA_MANAGE_PROPERTIES', 'manage object properties'); +define('ISLANDORA_VIEW_DATASTREAM_HISTORY', 'view old datastream versions'); +define('ISLANDORA_MANAGE_DELETED_OBJECTS', 'manage deleted objects'); +define('ISLANDORA_REVERT_DATASTREAM', 'revert to old datastream'); + // Hooks. define('ISLANDORA_VIEW_HOOK', 'islandora_view_object'); +define('ISLANDORA_PRINT_HOOK', 'islandora_view_print_object'); define('ISLANDORA_EDIT_HOOK', 'islandora_edit_object'); define('ISLANDORA_OVERVIEW_HOOK', 'islandora_overview_object'); define('ISLANDORA_PRE_INGEST_HOOK', 'islandora_ingest_pre_ingest'); define('ISLANDORA_POST_INGEST_HOOK', 'islandora_ingest_post_ingest'); define('ISLANDORA_PRE_PURGE_OBJECT_HOOK', 'islandora_pre_purge_object'); define('ISLANDORA_POST_PURGE_OBJECT_HOOK', 'islandora_post_purge_object'); +define('ISLANDORA_UPDATE_RELATED_OBJECTS_PROPERTIES_HOOK', 'islandora_update_related_objects_properties'); // @todo Add Documentation. define('ISLANDORA_OBJECT_INGESTED_HOOK', 'islandora_object_ingested'); @@ -51,10 +57,24 @@ define('ISLANDORA_DATASTREAM_INGESTED_HOOK', 'islandora_datastream_ingested'); define('ISLANDORA_DATASTREAM_MODIFIED_HOOK', 'islandora_datastream_modified'); define('ISLANDORA_DATASTREAM_PURGED_HOOK', 'islandora_datastream_purged'); define('ISLANDORA_INGEST_STEP_HOOK', 'islandora_ingest_steps'); +define('ISLANDORA_DERVIATIVE_CREATION_HOOK', 'islandora_derivative'); // Autocomplete paths. define('ISLANDORA_CONTENT_MODELS_AUTOCOMPLETE', 'islandora/autocomplete/content-models'); +/** + * @deprecated Constants. + */ +// @codingStandardsIgnoreStart +define('DS_COMP_STREAM', ISLANDORA_DS_COMP_STREAM); +define('FEDORA_VIEW_OBJECTS', ISLANDORA_VIEW_OBJECTS); +define('FEDORA_METADATA_EDIT', ISLANDORA_METADATA_EDIT); +define('FEDORA_ADD_DS', ISLANDORA_ADD_DS); +define('FEDORA_INGEST', ISLANDORA_INGEST); +define('FEDORA_PURGE', ISLANDORA_PURGE); +define('FEDORA_MANAGE_PROPERTIES', ISLANDORA_MANAGE_PROPERTIES); +// @codingStandardsIgnoreEnd + /** * Implements hook_menu(). * @@ -79,11 +99,19 @@ function islandora_menu() { 'type' => MENU_NORMAL_ITEM, 'weight' => -1, ); + $items['admin/islandora/metadata'] = array( + 'title' => 'Metadata Display', + 'description' => 'Configure settings for metadata display on Islandora objects', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('islandora_metadata_display_form'), + 'file' => 'includes/metadata.inc', + 'access arguments' => array('administer site configuration'), + ); $items['admin/islandora/solution_packs'] = array( 'title' => 'Solution packs', 'description' => 'Install content models and collections required by installed solution packs.', 'page callback' => 'islandora_solution_packs_admin', - 'access arguments' => array(FEDORA_ADD_DS), + 'access arguments' => array(ISLANDORA_ADD_DS), 'file' => 'includes/solution_packs.inc', 'type' => MENU_NORMAL_ITEM, ); @@ -91,7 +119,7 @@ function islandora_menu() { 'title' => 'Islandora Repository', 'page callback' => 'islandora_view_default_object', 'type' => MENU_NORMAL_ITEM, - 'access arguments' => array(FEDORA_VIEW_OBJECTS), + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS), ); $items['islandora/object/%islandora_object'] = array( 'title callback' => 'islandora_drupal_title', @@ -100,7 +128,23 @@ function islandora_menu() { 'page arguments' => array(2), 'type' => MENU_NORMAL_ITEM, 'access callback' => 'islandora_object_access_callback', - 'access arguments' => array(FEDORA_VIEW_OBJECTS, 2), + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2), + ); + $items['islandora/object/%islandora_object/print_object'] = array( + 'page callback' => 'islandora_printer_object', + 'page arguments' => array(2), + 'type' => MENU_NORMAL_ITEM, + 'access callback' => 'islandora_object_access', + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2), + ); + $items['islandora/object/%islandora_object/print'] = array( + 'title' => 'Print Object', + 'page callback' => 'islandora_print_object', + 'page arguments' => array(2), + 'type' => MENU_CALLBACK, + 'access callback' => 'islandora_object_access', + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2), + 'load arguments' => array(2), ); $items['islandora/object/%islandora_object/view'] = array( 'title' => 'View', @@ -120,20 +164,18 @@ function islandora_menu() { 'access callback' => 'islandora_object_manage_access_callback', 'access arguments' => array( array( - FEDORA_MANAGE_PROPERTIES, - FEDORA_METADATA_EDIT, - FEDORA_ADD_DS, - FEDORA_PURGE, - FEDORA_INGEST, + ISLANDORA_MANAGE_PROPERTIES, + ISLANDORA_METADATA_EDIT, + ISLANDORA_ADD_DS, + ISLANDORA_PURGE, + ISLANDORA_INGEST, ), 2), ); - $items['islandora/object/%islandora_object/manage/overview'] = array( 'title' => 'Overview', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -20, ); - $items['islandora/object/%islandora_object/manage/datastreams'] = array( 'title' => 'Datastreams', 'type' => MENU_LOCAL_TASK, @@ -142,13 +184,12 @@ function islandora_menu() { 'access callback' => 'islandora_object_manage_access_callback', 'access arguments' => array( array( - FEDORA_METADATA_EDIT, - FEDORA_ADD_DS, - FEDORA_PURGE, + ISLANDORA_METADATA_EDIT, + ISLANDORA_ADD_DS, + ISLANDORA_PURGE, ), 2), 'weight' => -10, ); - $items['islandora/object/%islandora_object/manage/properties'] = array( 'title' => 'Properties', 'page callback' => 'drupal_get_form', @@ -156,7 +197,7 @@ function islandora_menu() { 'page arguments' => array('islandora_object_properties_form', 2), 'type' => MENU_LOCAL_TASK, 'access callback' => 'islandora_object_access_callback', - 'access arguments' => array(FEDORA_MANAGE_PROPERTIES, 2), + 'access arguments' => array(ISLANDORA_MANAGE_PROPERTIES, 2), 'weight' => -5, ); $items['islandora/object/%islandora_object/delete'] = array( @@ -166,7 +207,7 @@ function islandora_menu() { 'page arguments' => array('islandora_delete_object_form', 2), 'type' => MENU_CALLBACK, 'access callback' => 'islandora_object_access_callback', - 'access arguments' => array(FEDORA_PURGE, 2), + 'access arguments' => array(ISLANDORA_PURGE, 2), ); $items['islandora/object/%islandora_object/manage/datastreams/add'] = array( 'title' => 'Add a datastream', @@ -175,7 +216,7 @@ function islandora_menu() { 'page arguments' => array('islandora_add_datastream_form', 2), 'type' => MENU_LOCAL_ACTION, 'access callback' => 'islandora_object_access_callback', - 'access arguments' => array(FEDORA_ADD_DS, 2), + 'access arguments' => array(ISLANDORA_ADD_DS, 2), ); $items['islandora/object/%islandora_object/manage/datastreams/add/autocomplete'] = array( 'file' => 'includes/add_datastream.form.inc', @@ -183,16 +224,16 @@ function islandora_menu() { 'page arguments' => array(2), 'type' => MENU_CALLBACK, 'access callback' => 'islandora_object_access_callback', - 'access arguments' => array(FEDORA_ADD_DS, 2), + 'access arguments' => array(ISLANDORA_ADD_DS, 2), ); $items['islandora/object/%islandora_object/datastream/%islandora_datastream'] = array( 'title' => 'View datastream', 'page callback' => 'islandora_view_datastream', - 'page arguments' => array(4, FALSE), + 'page arguments' => array(4, FALSE, NULL), 'type' => MENU_CALLBACK, 'file' => 'includes/datastream.inc', 'access callback' => 'islandora_datastream_access', - 'access arguments' => array(FEDORA_VIEW_OBJECTS, 4), + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 4), 'load arguments' => array(2), ); // This menu item uses token authentication in islandora_tokened_object. @@ -200,7 +241,7 @@ function islandora_menu() { 'title' => 'View datastream', 'load arguments' => array('%map'), 'access callback' => 'islandora_object_datastream_tokened_access_callback', - 'access arguments' => array(FEDORA_VIEW_OBJECTS, 2, 4), + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2, 4), 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['islandora/object/%islandora_object/datastream/%islandora_datastream/download'] = array( @@ -210,7 +251,7 @@ function islandora_menu() { 'type' => MENU_CALLBACK, 'file' => 'includes/datastream.inc', 'access callback' => 'islandora_datastream_access', - 'access arguments' => array(FEDORA_VIEW_OBJECTS, 4), + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 4), 'load arguments' => array(2), ); $items['islandora/object/%islandora_object/datastream/%islandora_datastream/edit'] = array( @@ -220,7 +261,7 @@ function islandora_menu() { 'type' => MENU_CALLBACK, 'file' => 'includes/datastream.inc', 'access callback' => 'islandora_datastream_access', - 'access arguments' => array(FEDORA_METADATA_EDIT, 4), + 'access arguments' => array(ISLANDORA_METADATA_EDIT, 4), 'load arguments' => array(2), ); $items['islandora/object/%islandora_object/datastream/%islandora_datastream/delete'] = array( @@ -230,16 +271,47 @@ function islandora_menu() { 'file' => 'includes/delete_datastream.form.inc', 'type' => MENU_CALLBACK, 'access callback' => 'islandora_datastream_access', - 'access arguments' => array(FEDORA_PURGE, 4), + 'access arguments' => array(ISLANDORA_PURGE, 4), 'load arguments' => array(2), ); - $items['islandora/object/%islandora_object/print'] = array( - 'title' => 'Print Object', - 'page callback' => 'islandora_print_object', - 'page arguments' => array(2), + $items['islandora/object/%islandora_object/datastream/%islandora_datastream/version'] = array( + 'title' => 'Datastream Versions', + 'page arguments' => array(4), + 'page callback' => 'islandora_datastream_version_table', + 'file' => 'includes/datastream.version.inc', 'type' => MENU_CALLBACK, - 'access callback' => 'islandora_object_access', - 'access arguments' => array(FEDORA_VIEW_OBJECTS, 2), + 'access callback' => 'islandora_datastream_access', + 'access arguments' => array(ISLANDORA_VIEW_DATASTREAM_HISTORY, 4), + 'load arguments' => array(2), + ); + $items['islandora/object/%islandora_object/datastream/%islandora_datastream/version/%/delete'] = array( + 'title' => 'Delete datastream version', + 'page arguments' => array('islandora_delete_datastream_version_form', 4, 6), + 'page callback' => 'drupal_get_form', + 'file' => 'includes/datastream.version.inc', + 'type' => MENU_CALLBACK, + 'access callback' => 'islandora_datastream_access', + 'access arguments' => array(ISLANDORA_PURGE, 4), + 'load arguments' => array(2), + ); + $items['islandora/object/%islandora_object/datastream/%islandora_datastream/version/%/revert'] = array( + 'title' => 'Revert to datastream version', + 'page arguments' => array('islandora_revert_datastream_version_form', 4, 6), + 'page callback' => 'drupal_get_form', + 'file' => 'includes/datastream.version.inc', + 'type' => MENU_CALLBACK, + 'access callback' => 'islandora_datastream_access', + 'access arguments' => array(ISLANDORA_REVERT_DATASTREAM, 4), + 'load arguments' => array(2), + ); + $items['islandora/object/%islandora_object/datastream/%islandora_datastream/version/%/view'] = array( + 'title' => 'View datastream version', + 'page callback' => 'islandora_view_datastream', + 'page arguments' => array(4, FALSE, 6), + 'type' => MENU_CALLBACK, + 'file' => 'includes/datastream.inc', + 'access callback' => 'islandora_datastream_access', + 'access arguments' => array(ISLANDORA_VIEW_DATASTREAM_HISTORY, 4), 'load arguments' => array(2), ); $items['islandora/object/%islandora_object/download_clip'] = array( @@ -247,9 +319,16 @@ function islandora_menu() { 'page arguments' => array(2), 'type' => MENU_CALLBACK, 'access callback' => 'islandora_object_access', - 'access arguments' => array(FEDORA_VIEW_OBJECTS, 2), + 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2), 'load arguments' => array(2), ); + $items['islandora/event-status'] = array( + 'title' => 'Event Status', + 'page callback' => 'islandora_event_status', + 'type' => MENU_CALLBACK, + 'access callback' => TRUE, + 'file' => 'includes/utilities.inc', + ); $items[ISLANDORA_CONTENT_MODELS_AUTOCOMPLETE] = array( 'title' => 'Autocomplete callback', 'description' => 'Autocomplete a Fedora content model PID.', @@ -259,6 +338,26 @@ function islandora_menu() { 'access arguments' => array('administer site configuration'), 'type' => MENU_CALLBACK, ); + $items['admin/islandora/restore/prep'] = array( + 'description' => 'Restore or permanantly remove objects with Deleted status', + 'title' => 'Manage Deleted Objects', + 'file' => 'includes/manage_deleted_objects.inc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('islandora_deleted_objects_prep_form'), + 'type' => MENU_NORMAL_ITEM, + 'access arguments' => array(ISLANDORA_MANAGE_DELETED_OBJECTS), + ); + + $items['admin/islandora/restore/manage'] = array( + 'description' => 'Restore or permanantly remove objects with Deleted status', + 'title' => 'Manage Deleted Objects', + 'file' => 'includes/manage_deleted_objects.inc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('islandora_deleted_objects_manage_form'), + 'type' => MENU_SUGGESTED_ITEM, + 'access arguments' => array(ISLANDORA_MANAGE_DELETED_OBJECTS), + ); + return $items; } @@ -268,8 +367,9 @@ function islandora_menu() { function islandora_admin_paths() { $paths = array(); $paths['islandora/object/*/manage*'] = TRUE; - $paths['islandora/object/*/delete'] = TRUE; + $paths['islandora/object/*/delete*'] = TRUE; $paths['islandora/object/*/datastream/*/edit'] = TRUE; + $paths['islandora/object/*/datastream/*/versions'] = TRUE; return $paths; } @@ -295,11 +395,20 @@ function islandora_theme() { 'file' => 'includes/solution_packs.inc', 'render element' => 'form', ), - // Print object view. + // Print used by the clipper. 'islandora_object_print' => array( 'file' => 'theme/theme.inc', 'variables' => array('object' => NULL, 'content' => array()), ), + // Print object view, prints islandora objects. + 'islandora_object_print_object' => array( + 'file' => 'theme/theme.inc', + 'template' => 'theme/islandora-object-print', + 'variables' => array( + 'object' => NULL, + 'content' => NULL, + 'islandora_content' => NULL), + ), // Render a bunch of objects as either a grid or a list. 'islandora_objects' => array( 'file' => 'theme/theme.inc', @@ -329,16 +438,53 @@ function islandora_theme() { ), 'islandora_datastream_delete_link' => array( 'file' => 'theme/theme.inc', - 'variables' => array('datastream' => NULL), + 'variables' => array('datastream' => NULL, 'version' => NULL), + ), + 'islandora_datastream_revert_link' => array( + 'file' => 'theme/theme.inc', + 'variables' => array('datastream' => NULL, 'version' => NULL), ), 'islandora_datastream_view_link' => array( 'file' => 'theme/theme.inc', - 'variables' => array('datastream' => NULL), + 'variables' => array( + 'datastream' => NULL, + 'version' => NULL, + 'label' => NULL, + ), ), 'islandora_datastream_download_link' => array( 'file' => 'theme/theme.inc', 'variables' => array('datastream' => NULL), ), + 'islandora_datastream_version_link' => array( + 'file' => 'theme/theme.inc', + 'variables' => array('datastream' => NULL), + ), + 'islandora_dublin_core_display' => array( + 'file' => 'theme/theme.inc', + 'template' => 'theme/islandora-dublin-core-display', + // We can add PIDs to the end of this pattern in our preprocess function + // and templates will be able to have have a pid appended to the + // template name to overide a template on a per object basis. + // An example template might be named: + // "islandora-dublin-core-display--islandora-27.tpl.php". + 'pattern' => 'islandora_dublin_core_display__', + 'variables' => array( + 'islandora_object' => NULL, + 'print' => NULL, + ), + ), + 'islandora_dublin_core_description' => array( + 'file' => 'theme/theme.inc', + 'template' => 'theme/islandora-dublin-core-description', + // We can add PIDs to the end of this pattern in our preprocess function + // and templates will be able to have have a pid appended to the + // template name to overide a template on a per object basis. + // An example template might be named: + // "islandora-dublin-core-description--islandora-27.tpl.php". + 'pattern' => 'islandora_dublin_core_description__', + 'variables' => array('islandora_object' => NULL), + ), ); } @@ -347,42 +493,111 @@ function islandora_theme() { */ function islandora_permission() { return array( - FEDORA_VIEW_OBJECTS => array( + ISLANDORA_VIEW_OBJECTS => array( 'title' => t('View repository objects'), 'description' => t('View objects in the repository. Note: Fedora XACML security policies may override this permission.'), ), - FEDORA_ADD_DS => array( + ISLANDORA_ADD_DS => array( 'title' => t('Add datastreams to repository objects'), 'description' => t('Add datastreams to objects in the repository. Note: Fedora XACML security policies may override this position.'), ), - FEDORA_METADATA_EDIT => array( + ISLANDORA_METADATA_EDIT => array( 'title' => t('Edit metadata'), 'description' => t('Edit metadata for objects in the repository.'), ), - FEDORA_INGEST => array( + ISLANDORA_INGEST => array( 'title' => t('Create new repository objects'), 'description' => t('Create new objects in the repository.'), ), - FEDORA_PURGE => array( + ISLANDORA_PURGE => array( 'title' => t('Permanently remove objects from the repository'), 'description' => t('Permanently remove objects from the repository.'), ), - FEDORA_MANAGE_PROPERTIES => array( + ISLANDORA_MANAGE_PROPERTIES => array( 'title' => t('Manage object properties'), 'description' => t('Modify object labels, owner IDs, and states.'), ), + ISLANDORA_VIEW_DATASTREAM_HISTORY => array( + 'title' => t('View datastream history'), + 'description' => t('View all previous versions of a datastream.'), + ), + ISLANDORA_REVERT_DATASTREAM => array( + 'title' => t('Revert datastream history'), + 'description' => t('Revert to a previous version of a datastream.'), + ), + ISLANDORA_MANAGE_DELETED_OBJECTS => array( + 'title' => t('Manage deleted objects'), + 'description' => t('Purge or revert deleted objects.'), + ), + ); +} + +/** + * Implements hook_i18n_string_info(). + */ +function islandora_i18n_string_info() { + return array( + 'islandora' => array( + 'title' => t('Islandora'), + 'description' => t('Translatable Metadata labels, etc.'), + 'format' => FALSE, + 'list' => TRUE, + ), ); } +/** + * Implements islandora_i18n_string_list(). + */ +function islandora_i18n_string_list($group) { + if ($group == 'islandora') { + return array( + 'islandora' => array( + 'dc' => array( + 'title' => array('label' => 'Title'), + 'creator' => array('label' => 'Creator'), + 'subject' => array('label' => 'Subject'), + 'description' => array('label' => 'Description'), + 'publisher' => array('label' => 'Publisher'), + 'contributor' => array('label' => 'Contributor'), + 'date' => array('label' => 'Date'), + 'type' => array('label' => 'Type'), + 'format' => array('label' => 'Format'), + 'identifier' => array('label' => 'Identifier'), + 'source' => array('label' => 'Source'), + 'language' => array('label' => 'Language'), + 'relation' => array('label' => 'Relation'), + 'coverage' => array('label' => 'Coverage'), + ), + ), + ); + } +} + +/** + * Renders the print page for the given object. + * + * Modules can either implement preprocess functions to append content onto the + * 'content' variable, or override the display by providing a theme suggestion. + * + * @param AbstractObject $object + * The object. + * + * @return array + * A renderable array. + */ +function islandora_print_object(AbstractObject $object) { + drupal_set_title($object->label); + return theme('islandora_object_print', array('object' => $object)); +} + /** * Implements hook_forms(). */ function islandora_forms($form_id) { $forms = array(); if (strpos($form_id, 'islandora_solution_pack_form_') !== FALSE) { - $forms[$form_id] = array( - 'callback' => 'islandora_solution_pack_form', - ); + $forms[$form_id] = array('callback' => 'islandora_solution_pack_form'); } return $forms; } @@ -651,11 +866,7 @@ function islandora_manage_overview_object(AbstractObject $object) { */ function islandora_default_islandora_manage_overview_object(AbstractObject $object) { $output = theme('islandora_default_overview', array('islandora_object' => $object)); - return array( - 'Default overview output' => array( - '#markup' => $output, - ), - ); + return array('Default overview output' => array('#markup' => $output)); } /** @@ -708,11 +919,7 @@ function islandora_edit_object(AbstractObject $object) { */ function islandora_default_islandora_edit_object(AbstractObject $object) { $output = theme('islandora_default_edit', array('islandora_object' => $object)); - return array( - 'Default Edit output' => array( - '#markup' => $output, - ), - ); + return array('Default Edit output' => array('#markup' => $output)); } /** @@ -744,8 +951,21 @@ function islandora_view_default_object() { function islandora_view_object(AbstractObject $object) { module_load_include('inc', 'islandora', 'includes/breadcrumb'); module_load_include('inc', 'islandora', 'includes/utilities'); + + // Add the print button via JavaScript. + $path = drupal_get_path('module', 'islandora'); + drupal_add_js(array( + 'islandora' => array( + 'print_img' => $path . '/images/print-icon.png'), + ), array( + 'type' => 'setting')); + + drupal_add_js(array('islandora' => array('print_link' => 'islandora/object/' . $object->id . '/print_object')), array('type' => 'setting')); + drupal_add_js($path . '/js/add_print.js'); + drupal_set_title($object->label); drupal_set_breadcrumb(islandora_get_breadcrumbs($object)); + // Optional pager parameters. $page_number = (empty($_GET['page'])) ? '1' : $_GET['page']; $page_size = (empty($_GET['pagesize'])) ? '10' : $_GET['pagesize']; @@ -764,12 +984,47 @@ function islandora_view_object(AbstractObject $object) { // No results, use the default view. $output = islandora_default_islandora_view_object($object); } + arsort($output); drupal_alter($hooks, $object, $output); islandora_as_renderable_array($output); return $output; } +/** + * This will prepare an object to be printed. + * + * By default, all fedora objects can print DC record data, + * however, Solution packs that wish to modify the form + * to be printed must implement hook_islandora_view_print_object_alter, + * create a theme.tpl.php file, and return its markup by calling + * theme(themename.tpl.php). + * + * @param AbstractObject $object + * The object to print. + * + * @return string + * An HTML representation of this object. + */ +function islandora_printer_object(AbstractObject $object) { + $output = array(); + $temp_arr = array(); + // Dispatch print hook. + foreach (islandora_build_hook_list(ISLANDORA_PRINT_HOOK, $object->models) as $hook) { + $temp = module_invoke_all($hook, $object); + islandora_as_renderable_array($temp); + if (!empty($temp)) { + $temp_arr = array_merge_recursive($temp_arr, $temp); + } + } + $output = islandora_default_islandora_printer_object($object, drupal_render($temp_arr)); + arsort($output); + islandora_as_renderable_array($output); + + // Prompt to print. + drupal_add_js('jQuery(document).ready(function () { window.print(); });', 'inline'); + return $output; +} /** * Title callback for drupal title. @@ -787,8 +1042,9 @@ function islandora_drupal_title(AbstractObject $object) { module_load_include('inc', 'islandora', 'includes/breadcrumb'); drupal_set_breadcrumb(islandora_get_breadcrumbs($object)); - return $object->label; + return filter_xss($object->label); } + /** * Renders the default view object page for the given object. * @@ -800,11 +1056,47 @@ function islandora_drupal_title(AbstractObject $object) { */ function islandora_default_islandora_view_object($object) { $output = theme('islandora_default', array('islandora_object' => $object)); - return array( - 'Default output' => array( - '#markup' => $output, - ), - ); + return array('Default output' => array('#markup' => $output)); +} + +/** + * Append the image alter to the printable form. + * + * @param AbstractObject $object + * The fedora object to print. + * @param unknown $alter + * The string representation of the themed viewable object. + * + * @return array + * A renderable array + */ +function islandora_default_islandora_printer_object($object, $alter) { + module_load_include('inc', 'islandora', 'includes/utilities'); + module_load_include('inc', 'islandora', 'includes/datastream'); + module_load_include('inc', 'islandora', 'includes/metadata'); + + $path = drupal_get_path('module', 'islandora'); + drupal_add_css($path . '/css/islandora.print.css'); + + $islandora_object = islandora_object_load($object->id); + $repository = $islandora_object->repository; + + try { + $dc = $islandora_object['DC']->content; + $dc_object = DublinCore::importFromXMLString($dc); + } + catch (Exception $e) { + drupal_set_message(t('Error retrieving object %s %t', array('%s' => $islandora_object->id, '%t' => $e->getMessage())), 'error', FALSE); + } + $metadata = islandora_retrieve_metadata_markup($object, TRUE); + $variables = isset($dc_object) ? $dc_object->asArray() : array(); + $output = theme('islandora_object_print_object', array( + 'object' => $object, + 'dc_array' => $variables, + 'metadata' => $metadata, + 'islandora_content' => $alter)); + + return array('Default output' => array('#markup' => $output)); } /** @@ -821,6 +1113,7 @@ function islandora_default_islandora_view_object($object) { * A IslandoraTuque instance */ function islandora_get_tuque_connection($user = NULL, $url = NULL) { + module_load_include('inc', 'islandora', 'includes/tuque'); $tuque = &drupal_static(__FUNCTION__); if (!$tuque) { if (IslandoraTuque::exists()) { @@ -1023,18 +1316,16 @@ function islandora_islandora_required_objects(IslandoraTuque $connection) { return array( 'islandora' => array( 'title' => 'Islandora', - 'objects' => array( - $root_collection, - ), + 'objects' => array($root_collection), ), ); } /** - * Implements islandora_undeleteable_datastreams(). + * Implements hook_islandora_undeleteable_datastreams(). */ function islandora_islandora_undeletable_datastreams(array $models) { - return array('DC'); + return array('DC', 'RELS-EXT'); } /** @@ -1202,23 +1493,6 @@ function islandora_entity_property_info() { return $info; } -/** - * Renders the print page for the given object. - * - * Modules can either implement preprocess functions to append content onto the - * 'content' variable, or override the display by providing a theme suggestion. - * - * @param AbstractObject $object - * The object. - * - * @return array - * A renderable array. - */ -function islandora_print_object(AbstractObject $object) { - drupal_set_title($object->label); - return theme('islandora_object_print', array('object' => $object)); -} - /** * Menu callback downloads the given clip. */ @@ -1317,7 +1591,7 @@ function islandora_islandora_object_access($op, $object, $user) { /** * Hookable access callback for datastreams. * - * Requires the equivalent permissions on the object. + * Positive permissions on object access suggests on the datastream. */ function islandora_datastream_access($op, $datastream, $user = NULL) { $cache = &drupal_static(__FUNCTION__); @@ -1346,13 +1620,12 @@ function islandora_datastream_access($op, $datastream, $user = NULL) { $user, )); - // Neither the object nor the datastream check returned FALSE, and one in - // the object or datastream checks returned TRUE. + // The datastream check returned FALSE, and one in the object or datastream + // checks returned TRUE. $cache[$op][$datastream->parent->id][$datastream->id][$user->uid] = ( - !in_array(FALSE, $object_results, TRUE) && - !in_array(FALSE, $datastream_results, TRUE) && - (in_array(TRUE, $object_results, TRUE) || in_array(TRUE, $datastream_results, TRUE)) - ); + !in_array(FALSE, $datastream_results, TRUE) && + (in_array(TRUE, $object_results, TRUE) || in_array(TRUE, $datastream_results, TRUE)) + ); } return $cache[$op][$datastream->parent->id][$datastream->id][$user->uid]; @@ -1362,12 +1635,164 @@ function islandora_datastream_access($op, $datastream, $user = NULL) { * Implements hook_islandora_basic_collection_get_query_filters(). */ function islandora_islandora_basic_collection_get_query_filters() { + module_load_include('inc', 'islandora', 'includes/utilities'); $enforced = variable_get('islandora_namespace_restriction_enforced', FALSE); if ($enforced) { $namespace_array = islandora_get_allowed_namespaces(); $namespace_sparql = implode('|', $namespace_array); - return format_string('regex(str(?object), "info:fedora/(!namespaces):")', array( - '!namespaces' => $namespace_sparql, - )); + return format_string('regex(str(?object), "info:fedora/(!namespaces):")', array('!namespaces' => $namespace_sparql)); + } +} + +/** + * Implements hook_islandora_object_ingested(). + * + * On object ingestion we call the case of source_dsid being NULL only as + * the islandora_islandora_datastream_ingested hook will handle the cases + * where specific values of source_dsid can occur. + */ +function islandora_islandora_object_ingested(AbstractObject $object) { + module_load_include('inc', 'islandora', 'includes/derivatives'); + $logging_results = islandora_do_derivatives($object, array( + 'source_dsid' => NULL, + )); + islandora_derivative_logging($logging_results); +} + +/** + * Implements hook_islandora_datastream_ingested(). + * + * When a datastream is ingested we filter the derivatives on source_dsid being + * equal to the current ingested datastream's id. + */ +function islandora_islandora_datastream_ingested(AbstractObject $object, AbstractDatastream $datastream) { + module_load_include('inc', 'islandora', 'includes/derivatives'); + $logging_results = islandora_do_derivatives($object, array( + 'source_dsid' => $datastream->id, + )); + islandora_derivative_logging($logging_results); +} + +/** + * Implements hook_islandora_datastream_modified(). + * + * When a datastream is modified we filter the derivatives on source_dsid being + * equal to the current ingested datastream's id. Force is set to TRUE such that + * existing derivatives will be updated to reflect the change in the source. + */ +function islandora_islandora_datastream_modified(AbstractObject $object, AbstractDatastream $datastream) { + module_load_include('inc', 'islandora', 'includes/derivatives'); + $logging_results = islandora_do_derivatives($object, array( + 'source_dsid' => $datastream->id, + 'force' => TRUE, + )); + islandora_derivative_logging($logging_results); +} + +/** + * Implements hook_form_simpletest_test_form_alter(). + */ +function islandora_form_simpletest_test_form_alter(array &$form) { + module_load_include('inc', 'simpletest', 'simpletest.pages'); + $form['tests'] = array( + '#type' => 'fieldset', + '#title' => t('Tests'), + '#description' => t('Select the test(s) or test group(s) you would like to run, and click Run tests.

NOTE: Tests in groups prefixed with Islandora generally require a configuration file, found in the Islandora module "tests" folder, as well as the use of the Islandora Drupal filter. Before any tests are run, please ensure that your web server has the appropriate permissions to alter the drupal_filter.xml file in your Fedora config folder. Your original Islandora Drupal filter configuration will NOT be overwritten by tests; HOWEVER, if PHP exits abnormally before the filter is reset, please use the "Repair Drupal Filter" button below.'), + ); + + $form['tests']['table'] = array( + '#theme' => 'simpletest_test_table', + ); + + // Generate the list of tests arranged by group. + $groups = simpletest_test_get_all(); + foreach ($groups as $group => $tests) { + $form['tests']['table'][$group] = array( + '#collapsed' => TRUE, + ); + + foreach ($tests as $class => $info) { + $form['tests']['table'][$group][$class] = array( + '#type' => 'checkbox', + '#title' => filter_xss($info['name']), + '#description' => filter_xss($info['description']), + ); + } + } + + // Operation buttons. + $form['tests']['op'] = array( + '#type' => 'submit', + '#value' => t('Run tests'), + ); + + $form['reset'] = array( + '#type' => 'fieldset', + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#title' => t('Repair Islandora Drupal Filter'), + '#description' => t('Attempts to repair the Islandora Drupal filter if a test that alters the Drupal filter has crashed. This is also intended for developers when creating tests built using the IslandoraWebTestCase class.'), + ); + $form['reset']['op'] = array( + '#type' => 'submit', + '#value' => t('Repair Drupal Filter'), + '#submit' => array('islandora_repair_drupal_filter'), + ); +} + +/** + * Submit handler for islandora_form_simpletest_test_form_alter(). + */ +function islandora_repair_drupal_filter() { + + // Grab the config. + $path = drupal_get_path('module', 'islandora'); + if (file_exists("$path/tests/test_config.ini")) { + $configuration = parse_ini_file("$path/tests/test_config.ini"); + } + elseif (file_exists("$path/tests/default.test_config.ini")) { + $configuration = parse_ini_file("$path/tests/default.test_config.ini"); } + else { + drupal_set_message(t('Required default.test_config.ini/test_config.ini file not found'), 'error'); + return FALSE; + } + + // Xpath to the filter 'sql' elements. + $drupal_filter_dom = new DOMDocument(); + $drupal_filter_dom->loadXML(file_get_contents($configuration['drupal_filter_file'])); + $drupal_filter_xpath = new DOMXPath($drupal_filter_dom); + + // Blow out the simpletest stuff. + $entries = 0; + foreach ($drupal_filter_xpath->query('//sql') as $sql) { + if (strpos($sql->nodeValue, 'simpletest') !== FALSE) { + $parent = $sql->parentNode; + $root = $parent->parentNode; + $parent->removeChild($sql); + $root->removeChild($parent); + $entries++; + } + } + file_put_contents($configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); + if ($entries == 0) { + drupal_set_message(t("No simpletest entries were found in the Drupal filter.")); + } + else { + drupal_set_message(format_plural($entries, "Removed 1 simpletest entry from the Drupal filter.", "Removed @count simpletest entries from the Drupal filter.")); + } +} + + /** + * Implements hook_islandora_metadata_display_info(). + */ +function islandora_islandora_metadata_display_info() { + return array( + 'dublin_core' => array( + 'label' => t('Dublin Core'), + 'description' => t('Dublin Core metadata'), + 'metadata callback' => 'islandora_metadata_display_callback', + 'description callback' => 'islandora_metadata_description_callback', + ), + ); } diff --git a/js/add_print.js b/js/add_print.js new file mode 100644 index 00000000..0059ed88 --- /dev/null +++ b/js/add_print.js @@ -0,0 +1,18 @@ +/** + * @file +* JavaScript file responsable for the print button behaviour. +* +* The print button is added automatically to every view, as metadata +* can be printed from every object. +* +*/ +(function ($) { + $(document).ready(function() { + $('.tabs .primary').append(''); + $('#print_btn').css("cursor","pointer"); + $('#print_btn').click(function() { + window.location=Drupal.settings.basePath + Drupal.settings.islandora.print_link; + }); + }); +})(jQuery); + diff --git a/js/spin/LICENSE.txt b/js/spin/LICENSE.txt new file mode 100644 index 00000000..b0a7912e --- /dev/null +++ b/js/spin/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2011 Felix Gnass [fgnass at neteye dot de] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/js/spin/README.md b/js/spin/README.md new file mode 100755 index 00000000..f88c257b --- /dev/null +++ b/js/spin/README.md @@ -0,0 +1,11 @@ +CONTENTS OF THIS FILE +--------------------- + + * summary + +SUMMARY +------- + +This directory contains the spin.js library: http://fgnass.github.io/spin.js/ +downloaded from http://fgnass.github.io/spin.js/dist/spin.min.js on Sept 27th +2013. This project is under version control: https://github.com/fgnass/spin.js. diff --git a/js/spin/spin.min.js b/js/spin/spin.min.js new file mode 100644 index 00000000..d4283919 --- /dev/null +++ b/js/spin/spin.min.js @@ -0,0 +1 @@ +(function(t,e){if(typeof exports=="object")module.exports=e();else if(typeof define=="function"&&define.amd)define(e);else t.Spinner=e()})(this,function(){"use strict";var t=["webkit","Moz","ms","O"],e={},i;function o(t,e){var i=document.createElement(t||"div"),o;for(o in e)i[o]=e[o];return i}function n(t){for(var e=1,i=arguments.length;e>1):parseInt(n.left,10)+s)+"px",top:(n.top=="auto"?l.y-a.y+(t.offsetHeight>>1):parseInt(n.top,10)+s)+"px"})}r.setAttribute("role","progressbar");e.lines(r,e.opts);if(!i){var d=0,p=(n.lines-1)*(1-n.direction)/2,c,h=n.fps,m=h/n.speed,y=(1-n.opacity)/(m*n.trail/100),g=m/n.lines;(function v(){d++;for(var t=0;t>1)+"px"})}for(;r',e)}r.addRule(".spin-vml","behavior:url(#default#VML)");c.prototype.lines=function(e,i){var o=i.length+i.width,r=2*o;function s(){return f(t("group",{coordsize:r+" "+r,coordorigin:-o+" "+-o}),{width:r,height:r})}var a=-(i.width+i.length)*2+"px",l=f(s(),{position:"absolute",top:a,left:a}),u;function p(e,r,a){n(l,n(f(s(),{rotation:360/i.lines*e+"deg",left:~~r}),n(f(t("roundrect",{arcsize:i.corners}),{width:o,height:i.width,left:i.radius,top:-i.width>>1,filter:a}),t("fill",{color:d(i.color,e),opacity:i.opacity}),t("stroke",{opacity:0}))))}if(i.shadow)for(u=1;u<=i.lines;u++)p(u,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(u=1;u<=i.lines;u++)p(u);return n(e,l)};c.prototype.opacity=function(t,e,i,o){var n=t.firstChild;o=o.shadow&&o.lines||0;if(n&&e+o').text(settings.spinner[base].message); + $(id).after(message); + // Make UI changes. + spinner.spin(this); + $('#edit-next').hide(); + $('#edit-prev').hide(); + // Submit the form after a set timeout, this handles problems with + // safari, in that safari submit's immediately.. + if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + $(':submit').attr('disabled', 'disabled'); + } + setTimeout(function() { + // Allow for the button to be clicked, then click it then + // prevent the default behavoir. + $(id).removeAttr('disabled') + .click() + .click(function(event) { + event.preventDefault(); + }); + }, 500); + } + return true; + }); + }); + } + } + }; +})(jQuery); diff --git a/tests/authtokens.test b/tests/authtokens.test index ccd3088a..352689e2 100644 --- a/tests/authtokens.test +++ b/tests/authtokens.test @@ -74,7 +74,7 @@ class IslandoraAuthtokensTestCase extends IslandoraWebTestCase { $this->drupalGet("islandora/object/{$newpid}/datastream/JP2/view"); $this->assertResponse(403, 'Page not found as anonymous'); - $account = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS)); + $account = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS)); $this->drupalLogin($account); $this->drupalGet("islandora/object/{$newpid}/datastream/JP2/view"); diff --git a/tests/datastream_cache.test b/tests/datastream_cache.test new file mode 100644 index 00000000..8118c08a --- /dev/null +++ b/tests/datastream_cache.test @@ -0,0 +1,160 @@ + 'Datastream Cache Headers', + 'description' => 'Check our headers work as we expect them to.', + 'group' => 'Islandora', + ); + } + + /** + * Creates an admin user and a connection to a fedora repository. + * + * @see IslandoraWebTestCase::setUp() + */ + public function setUp() { + parent::setUp(); + $this->repository = $this->admin->repository; + $this->purgeTestObjects(); + } + + /** + * Free any objects/resources created for this test. + * + * @see IslandoraWebTestCase::tearDown() + */ + public function tearDown() { + $this->purgeTestObjects(); + parent::tearDown(); + } + + /** + * Purge any objects created by the test's in this class. + */ + public function purgeTestObjects() { + $objects = array( + 'test:test', + ); + foreach ($objects as $object) { + try { + $object = $this->repository->getObject($object); + $this->repository->purgeObject($object->id); + } + catch (Exception $e) { + // Meh... Either it didn't exist or the purge failed. + } + } + } + + /** + * Create our test object. + */ + protected function createTestObject() { + $object = $this->repository->constructObject('test:test'); + $object->label = 'Test object'; + $object->models = 'test:model'; + $datastream = $object->constructDatastream('asdf', 'M'); + $datastream->label = 'datastream of doom'; + $datastream->mimetype = 'text/plain'; + $datastream->content = 'And then things happened.'; + $datastream->checksumType = 'SHA-1'; + $object->ingestDatastream($datastream); + $this->repository->ingestObject($object); + return $object; + } + + /** + * Test HTTP cache headers. + */ + public function testCacheHeaders() { + $object = $this->createTestObject(); + $datastream = $object['asdf']; + + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS)); + $this->drupalLogin($user); + + // Test If-Modified-Since. + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + 'If-Modified-Since: ' . $datastream->createdDate->format('D, d M Y H:i:s \G\M\T'), + )); + $this->assertResponse(304); + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + 'If-Modified-Since: ' . $datastream->createdDate->sub(new DateInterval('P1M'))->format('D, d M Y H:i:s \G\M\T'), + )); + $this->assertResponse(200); + + // Test If-Unmodified-Since. + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + 'If-Unmodified-Since: ' . $datastream->createdDate->format('D, d M Y H:i:s \G\M\T'), + )); + $this->assertResponse(200); + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + 'If-Unmodified-Since: ' . $datastream->createdDate->sub(new DateInterval('P1M'))->format('D, d M Y H:i:s \G\M\T'), + )); + $this->assertResponse(412); + + // Test If-Match. + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + format_string('If-Match: "!checksum"', array( + '!checksum' => $datastream->checksum, + )), + )); + $this->assertResponse(200); + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + format_string('If-Match: "!checksum"', array( + '!checksum' => 'dont-match' . $datastream->checksum, + )), + )); + $this->assertResponse(412); + + // Test If-None-Match. + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + format_string('If-None-Match: "!checksum"', array( + '!checksum' => $datastream->checksum, + )), + )); + $this->assertResponse(304); + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + format_string('If-None-Match: "!checksum"', array( + '!checksum' => 'dont-match' . $datastream->checksum, + )), + )); + $this->assertResponse(200); + + // Test combination of If-None-Match and If-Modified-Since + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + 'If-Modified-Since: ' . $datastream->createdDate->format('D, d M Y H:i:s \G\M\T'), + format_string('If-None-Match: "!checksum"', array( + '!checksum' => $datastream->checksum, + )), + )); + $this->assertResponse(304); + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + 'If-Modified-Since: ' . $datastream->createdDate->format('D, d M Y H:i:s \G\M\T'), + format_string('If-None-Match: "!checksum"', array( + '!checksum' => 'dont-match' . $datastream->checksum, + )), + )); + $this->assertResponse(200); + $result = $this->drupalGet("islandora/object/{$object->id}/datastream/{$datastream->id}/view", array(), array( + 'If-Modified-Since: ' . $datastream->createdDate->sub(new DateInterval('P1M'))->format('D, d M Y H:i:s \G\M\T'), + format_string('If-None-Match: "!checksum"', array( + '!checksum' => $datastream->checksum, + )), + )); + $this->assertResponse(200); + } +} diff --git a/tests/datastream_validators.inc b/tests/datastream_validators.inc new file mode 100644 index 00000000..4c69788b --- /dev/null +++ b/tests/datastream_validators.inc @@ -0,0 +1,473 @@ +content; + $results = array(); + $pass = "Image datastream {$datastream} is valid."; + $fail = "Image datastream {$datastream} is either invalid or corrupt."; + $results = islandora_assert_valid(imagecreatefromstring($datastream_string), $results, $pass, $fail); + return $results; +} + +/** + * Asserts the validity of any .tif/.tiff datastream. + * + * Does not use the islandora_assert_valid() function, as this is not a simple + * true/false. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID to check that corresponds to a .tif/.tiff datastream. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_tiff_datastream($object, $datastream) { + $datastream_string = $object[$datastream]->content; + $datastream_header_hex = substr(bin2hex($datastream_string), 0, 8); + $results = array(); + if ($datastream_header_hex == "49492a00") { + // In this case, the ingested TIFF is designated as using the "Intel + // byte-order" (e.g. little-endian) by starting with the characters "II" + // (repeated so that byte order does not yet need to be significant). + // The number that follows is '42' in little-endian hex, a number of + // 'deep philosophical significance' to the TIFF format creators. + array_push($results, array(TRUE, "{$datastream} datastream asserts that it is a valid Intel-byte-orderded TIF/TIFF file.")); + } + elseif ($datastream_header_hex == "4d4d002a") { + // In this case, the ingested TIFF is designated as using the "Motorola + // byte-order" (e.g. big-endian) by starting with the characters "MM" + // instead. 42 follows once again, this time in big-endian hex. + array_push($results, array(TRUE, "{$datastream} datastream asserts that it is a valid Motorola-byte-ordered TIF/TIFF file.")); + } + else { + array_push($results, array(FALSE, "{$datastream} datastream does not assert that it is a valid TIF/TIFF file.")); + } + return $results; +} + +/** + * Asserts the validity of any .jp2 datastream. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID to check that corresponds to a .jp2 datastream. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_jp2_datastream($object, $datastream) { + $datastream_hex = bin2hex($object[$datastream]->content); + $results = array(); + // JP2 files begin with an offset header at the second 32-bit integer, + // 0x6A502020. This header is in all .jp2s, and we check for it here. + $pass = "{$datastream} datastream begins correctly with the appropriate .jp2 header."; + $fail = "{$datastream} datastream does not begin with the appropriate .jp2 header."; + $results = islandora_assert_valid(substr($datastream_hex, 8, 8) == '6a502020', $results, $pass, $fail); + // JP2 files have their codestream capped with a marker, 0xFFD9. We're + // just checking for it here to see if the .jp2 encoder finished okay. + $pass = "{$datastream} datastream ends correctly with the appropriate .jp2 marker."; + $fail = "{$datastream} datastream does not end with a .jp2 marker; derivative generation was likely interrupted."; + $results = islandora_assert_valid(substr($datastream_hex, strlen($datastream_hex) - 4, 4) == 'ffd9', $results, $pass, $fail); + return $results; +} + +/** + * Asserts the validity of any .pdf datastream. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID to check that corresponds to a .pdf datastream. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_pdf_datastream($object, $datastream) { + $pdf = $object[$datastream]->content; + $pdf_version = substr($pdf, 5, 3); + $results = array(); + $pass = "{$datastream} datastream asserts that it is a valid PDF file using PDF version {$pdf_version}"; + $fail = "{$datastream} datastream binary header appears to be corrupt and missing a valid PDF signature."; + $results = islandora_assert_valid(substr($pdf, 0, 5) == '%PDF-', $results, $pass, $fail); + + $pdf_streams = substr_count(bin2hex($pdf), '0a73747265616d0a'); + $pass = "{$datastream} datastream reports the existence of {$pdf_streams} PDF streams. Note that an extremely low number could still indicate corruption."; + $fail = "{$datastream} datastream contains zero PDF streams, and is likely not a PDF file."; + $results = islandora_assert_valid($pdf_streams, $results, $pass, $fail); + + $pass = "{$datastream} datastream reports the existence of the closing 'EOF' tag required at the end of PDFs"; + $fail = "{$datastream} datastream does not contain the closing 'EOF' tag. If this is the only PDF validation that failed, it is likely that derivative generation was interrupted."; + $results = islandora_assert_valid(strpos(bin2hex($pdf), '0a2525454f460a'), $results, $pass, $fail); + return $results; +} + +/** + * Asserts that a string of text shows up inside a datastream. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID to check that corresponds to a datastream containing text. + * @param array $text + * An array of strings/the number of times it should appear in the datastream. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_text_datastream($object, $datastream, array $text) { + $results = array(); + $content = $object[$datastream]->content; + $string_count = substr_count($content, $text[0]); + $pass = "{$datastream} datastream contains the word(s) '{$text[0]}' repeated {$string_count} time(s) (expected: {$text[1]})."; + $fail = "{$datastream} datastream contains the word(s) '{$text[0]}' repeated {$string_count} time(s) (expected: {$text[1]})."; + $results = islandora_assert_valid($string_count == $text[1], $results, $pass, $fail); + return $results; +} + +/** + * Asserts the validity of any .wav datastraeam. + * + * WAV files contain a rigidly detailed header that contains all sorts of fun + * information we can use to validate things against other things. So, we check + * rigorously that the header contains properly constructed data by looking to + * see if certain values are at their expected byte offset. We also compare + * declared chunk sizes against actual sizes. If any of these are off, WAV + * players will fail to function. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID to check that corresponds to a datastream generated via OCR or HOCR. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_wav_datastream($object, $datastream) { + $results = array(); + $wav = bin2hex($object['OBJ']->content); + $wav_subchunk2size = islandora_hex2int(substr($wav, 80, 8)); + $wav_samplerate = islandora_hex2int(substr($wav, 48, 8)); + $wav_numchannels = islandora_hex2int(substr($wav, 44, 4)); + $wav_bytespersample = islandora_hex2int(substr($wav, 68, 4)) / 8; + $wav_numsamples = strlen(substr($wav, 88)) / $wav_numchannels / $wav_bytespersample / 2; + $magic_number = str_split(substr($wav, 0, 24), 8); + + $pass = "Header of the {$datastream} datastream contains correct file signature"; + $fail = "Header of the {$datastream} datastream contains corrupt file signature"; + $results = islandora_assert_valid($magic_number[0] = '52494646' && $magic_number[2] = '57415645', $results, $pass, $fail); + + $pass = "{$datastream} datastream chunksize in WAV header is correct"; + $fail = "{$datastream} datastream chunksize in WAV header does not match actual chunksize."; + $results = islandora_assert_valid(islandora_hex2int(substr($wav, 8, 8)) === 36 + $wav_subchunk2size, $results, $pass, $fail); + + $pass = "{$datastream} datastream contains a 'fmt' subchunk."; + $fail = "{$datastream} datastream is missing the required 'fmt' subchunk."; + $results = islandora_assert_valid(substr($wav, 24, 8) === '666d7420', $results, $pass, $fail); + + $pass = "{$datastream} datastream byterate in the WAV header is correct."; + $fail = "{$datastream} datastream byterate in the WAV header does not match actual calculated byterate."; + $results = islandora_assert_valid(islandora_hex2int(substr($wav, 56, 8)) === $wav_samplerate * $wav_numchannels * $wav_bytespersample, $results, $pass, $fail); + + $pass = "{$datastream} datastream block alignment is set correctly."; + $fail = "{$datastream} datastream block alignment is off."; + $results = islandora_assert_valid(islandora_hex2int(substr($wav, 64, 4)) === $wav_numchannels * $wav_bytespersample, $results, $pass, $fail); + + $pass = "{$datastream} datastream contains 'data' subchunk."; + $fail = "{$datastream} datastream is missing the 'data' subchunk."; + $results = islandora_assert_valid(substr($wav, 72, 8) === '64617461', $results, $pass, $fail); + + $pass = "{$datastream} datastream 'data' chunk is the correct size."; + $fail = "{$datastream} datastream 'data' chunk is sized incorrectly."; + $results = islandora_assert_valid($wav_subchunk2size === $wav_numsamples * $wav_numchannels * $wav_bytespersample, $results, $pass, $fail); + + return $results; +} + +/** + * Asserts the validity of any .mp3 datastream. + * + * Our default setup tries to create an MP3 using VBR, but we do some extra + * checks in case someone turns that off. If the header contains the characters + * 'Xing', it is flagged as VBR, and we can do an in-depth check on each of the + * VBR settings. Otherwise, we look for the basic MP3 signature 'fffa' or 'fffb' + * at the start of the binary. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID of a datastream corresponding to an mp3 file. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_mp3_datastream($object, $datastream) { + $results = array(); + $mp3 = bin2hex($object[$datastream]->content); + $mp3_size = strlen($mp3) / 2; + + // Looks to see if VBR was set properly by LAME. If so, MATH TIME! + if (strpos($mp3, '58696e67')) { + $mp3_vbrheader = substr($mp3, strpos($mp3, '58696e67'), 240); + + // Check the field flags. VBR-formatted MP3 files contain a 32-bit + // integer (stored as $mp3_flag_value) that is a combination of four + // bits, each one indicating the on-off status of a VBR setting, via + // logical OR. Rather than disassembling this value into individual + // bits, we use the algorithm "if (binary_total+bit_value*2)/bit_value*2 + // is greater than or equal to bit_value, that bit is turned on" to find + // the status of each bit, so we know whether to offset the rest. + $mp3_field_offset = array(0, 0, 0); + $mp3_flag_value = hexdec(substr($mp3_vbrheader, 8, 8)); + + // We can't use the first flag, but we still need to offset the rest. + if (($mp3_flag_value + 1) % 2 == 0) { + $mp3_field_offset[0] += 8; + $mp3_field_offset[1] += 8; + $mp3_field_offset[2] += 8; + } + + // The second flag leads us to filesize data, which we can verify. + if (($mp3_flag_value + 4) % 4 > 1) { + $mp3_field_bytes = hexdec(substr($mp3_vbrheader, $mp3_field_offset[0] + 16, 8)); + $pass = "{$datastream} datastream reported filesize of {$mp3_size} bytes matches size field value of {$mp3_field_bytes}"; + $fail = "{$datastream} datastream reported filesize of {$mp3_size} bytes does not match size field value of {$mp3_field_bytes}"; + $results = islandora_assert_valid($mp3_size == $mp3_field_bytes, $results, $pass, $fail); + $mp3_field_offset[1] += 8; + $mp3_field_offset[2] += 8; + } + + // We can't use the third flag for anything either. + if (($mp3_flag_value + 8) % 8 > 3) { + $mp3_field_offset[2] += 200; + } + + // The fourth flag leads us to VBR quality data, which we can validate. + if ($mp3_flag_value > 7) { + $mp3_field_quality = hexdec(substr($mp3_vbrheader, $mp3_field_offset[2] + 16, 8)); + $pass = "{$datastream} datastream reports valid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; + $fail = "{$datastream} datastream reports invalid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; + $results = islandora_assert_valid($mp3_field_quality <= 100 && $mp3_field_quality >= 0, $results, $pass, $fail); + } + } + + // Otherwise, just forget everything and check the file signature. + elseif (strpos($mp3, '58696e67') == FALSE && substr($mp3, 0, 4) == 'fffa') { + $results = array(array(TRUE, "{$datastream} datastream is encoded as a valid MPEG-1 Layer 3 file with CRC protection")); + } + elseif (strpos($mp3, '58696e67') == FALSE && substr($mp3, 0, 4) == 'fffb') { + $results = array(array(TRUE, "{$datastream} datastream is encoded as a valid unprotected MPEG-1 Layer 3 file")); + } + else { + $results = array(array(FALSE, "{$datastream} datastream is corrupt and does not identify as a valid MP3.")); + } + return $results; +} + +/** + * Attempts to validate an .mp4 datastream. + * + * MP4 files are a subset of the ISO file format specification, and as such need + * to contain a 64-bit declaration of type within the first eight eight bytes of + * the file. This declaration is comprised of the characters 'ftyp', followed by + * a four-character filetype code. Below, we look for 'ftyp', and then pass the + * filetype code to the test message. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID of a datastream corresponding to an mp4 file. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_mp4_datastream($object, $datastream) { + $results = array(); + $mp4 = $object[$datastream]->content; + if (strpos($mp4, 'ftyp')) { + $mp4_ftyp = substr(strpos($mp4, 'ftyp'), 4, 4); + } + $pass = "{$datastream} datastream asserts that it is a valid ISO-formatted video file using ftyp {$mp4_ftyp}"; + $fail = "{$datastream} datastream is not a valid ISO-formatted video"; + $results = islandora_assert_valid(strpos($mp4, 'ftyp'), $results, $pass, $fail); + return $results; +} + +/** + * Attempts to validate an .ogg/ogv datastream using Vorbis and Theora encoding. + * + * OGG files are made up of several 'pages' of OGG data, each prefaced with an + * OGG marker - the letters 'OggS'. The file header also contains information on + * what encoders were used to create the file. Here, we're looking for at least + * one OGG page, and confirming that the file asserts the Theora and Vorbis + * codecs were used to create the file. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID of a datastream corresponding to an ogg file. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_ogg_datastream($object, $datastream) { + $results = array(); + $ogg = $object[$datastream]->content; + $ogg_pages = substr_count($ogg, 'OggS'); + + $pass = "{$datastream} datastream asserts that it contains {$ogg_pages} Ogg pages (even a very small file should contain several)."; + $fail = "{$datastream} datastream contains no Ogg pages."; + $results = islandora_assert_valid(substr_count($ogg, 'OggS'), $results, $pass, $fail); + + $pass = "{$datastream} datastream asserts that it contains Theora-encoded video data."; + $fail = "{$datastream} datastream contains no marker indicating the presence of Theora-encoded video data."; + $results = islandora_assert_valid(substr_count($ogg, 'theora'), $results, $pass, $fail); + + $pass = "{$datastream} datastream asserts that it contains Vorbis-encoded audio data"; + $fail = "{$datastream} datastream contains no marker indicating the presence of Vorbis-encoded audio data."; + $results = islandora_assert_valid(substr_count($ogg, 'vorbis'), $results, $pass, $fail); + + return $results; +} + +/** + * Attempts to validate an .mkv datastream. + * + * There's not much we can do to check an MKV file, since the format is really, + * really loose. We do know a couple of things though - first, since MKV is an + * EBML format, the first four characters will always be the same. Since they're + * non-standard characters, we're looking at their hex values instead. And + * second, we know that the file will contain the declaration 'matroska' soon + * after. We could look for this in the binary, but we already have the hex- + * translated version, so we just look for 'matroska' in hex. + * + * @param AbstractObject $object + * The PID of the object. + * @param string $datastream + * A DSID of a datastream corresponding to an MKV file. + * + * @return array + * A series of TRUE(pass)/FALSE(fail) results paired with result messages. + */ +function islandora_validate_mkv_datastream($object, $datastream) { + $results = array(); + $mkv = bin2hex($object[$datastream]->content); + + $pass = "{$datastream} datastream asserts that it is an EBML-formatted file"; + $fail = "{$datastream} datastream is not an EBML-formatted file."; + $results = islandora_assert_valid(substr($mkv, 0, 8) == '1a45dfa3', $results, $pass, $fail); + + $pass = "{$datastream} datastream asserts that its EBML DocType is Matroska"; + $fail = "{$datastream} datastream does not contain a Matroska EBML DocType marker."; + $results = islandora_assert_valid(substr_count($mkv, '6d6174726f736b61') == 1, $results, $pass, $fail); + + return $results; +} diff --git a/tests/datastream_versions.test b/tests/datastream_versions.test new file mode 100644 index 00000000..6b22bd95 --- /dev/null +++ b/tests/datastream_versions.test @@ -0,0 +1,198 @@ + 'Islandora Datastream Versions', + 'description' => 'Tests the functionality related to datastream versions in Islandora.', + 'group' => 'Islandora', + ); + } + + /** + * Create an object with many datastram versions. + * + * @see IslandoraWebTestCase::setUp() + */ + public function setUp() { + parent::setUp(); + + $this->pid = $this->randomName() . ":" . $this->randomName(); + $tuque = islandora_get_tuque_connection(); + $object = $tuque->repository->constructObject($this->pid); + $object = $tuque->repository->ingestObject($object); + $this->dsid = $this->randomName(); + $ds = $object->constructDatastream($this->dsid); + $ds->label = 'Test'; + $ds->content = 'test'; + $object->ingestDatastream($ds); + + // Create three versions. + $ds->mimetype = 'application/pdf'; + $ds->label = 'jaaaaa maaaan'; + $ds->content = 'Tests... are the bests.'; + } + + /** + * Free any objects/resources created for this test. + * + * @see IslandoraWebTestCase::tearDown() + */ + public function tearDown() { + $tuque = islandora_get_tuque_connection(); + $tuque->repository->purgeObject($this->pid); + + parent::tearDown(); + } + + /** + * Check if the user can see datastream versions in the datastream table. + */ + public function testSeeDatastreamVersions() { + $user = $this->drupalCreateUser(array( + 'view fedora repository objects', + 'ingest fedora objects', + 'view old datastream versions', + 'add fedora datastreams', + )); + $this->drupalLogin($user); + $this->drupalGet("islandora/object/{$this->pid}/manage/datastreams"); + $this->assertLink($this->dsid); + $this->assertLink("4"); + $encoded_pid = urlencode($this->pid); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version"); + } + + /** + * Check that users without permission cannot see datastream versions. + */ + public function testNotSeeDatastreamVersions() { + $user = $this->drupalCreateUser(array( + 'view fedora repository objects', + 'ingest fedora objects', + 'add fedora datastreams', + )); + $this->drupalLogin($user); + $this->drupalGet("islandora/object/{$this->pid}/manage/datastreams"); + $this->assertLink($this->dsid); + $this->assertNoLink("4"); + $encoded_pid = urlencode($this->pid); + $this->assertNoLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version"); + } + + /** + * Check that users without permission cannot see datastream version pages. + */ + public function testDatastreamVersionPermissions() { + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version"); + $this->assertResponse(403); + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/0/view"); + $this->assertResponse(403); + } + + /** + * Check that the proper infomration is displayed on the ds version page. + */ + public function testDatastreamVersionPage() { + $user = $this->drupalCreateUser(array( + 'view old datastream versions', + )); + $this->drupalLogin($user); + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version"); + $this->assertNoLink("Delete"); + $this->assertText("text/xml"); + $this->assertText("application/pdf"); + $this->assertText("jaaaaa maaaan"); + $this->assertText("Test"); + + $encoded_pid = urlencode($this->pid); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/0/view"); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/1/view"); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/2/view"); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/3/view"); + } + + /** + * Make sure the correct content is displayed for each datastream version. + */ + public function testDatastreamVersionContent() { + $user = $this->drupalCreateUser(array( + 'view old datastream versions', + )); + $this->drupalLogin($user); + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/3/view"); + $content = $this->drupalGetContent(); + if ($content != 'test') { + $this->fail("Incorrect datastream content"); + } + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/2/view"); + $content = $this->drupalGetContent(); + if ($content != 'test') { + $this->fail("Incorrect datastream content"); + } + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/1/view"); + $content = $this->drupalGetContent(); + if ($content != 'test') { + $this->fail("Incorrect datastream content"); + } + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/0/view"); + $content = $this->drupalGetContent(); + if ($content != 'Tests... are the bests.') { + $this->fail("Incorrect datastream content"); + } + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/5/view"); + $this->assertResponse(404); + } + + /** + * Make sure you can delete datastream versions. + */ + public function testDatastreamVersionDelete() { + $user = $this->drupalCreateUser(array( + 'view old datastream versions', + 'delete fedora objects and datastreams', + )); + $this->drupalLogin($user); + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version"); + $this->assertLink("delete"); + $this->assertText('text/xml'); + + $encoded_pid = urlencode($this->pid); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/0/delete"); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/1/delete"); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/2/delete"); + $this->assertLinkByHref("islandora/object/$encoded_pid/datastream/{$this->dsid}/version/3/delete"); + + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/3/delete"); + $this->drupalPost(NULL, array(), t('Delete')); + $this->assertNoText('text/xml'); + } + + /** + * Make sure you can't delete versions that don't exist/have only 1 version. + */ + public function testDatastreamVersionDeleteEdgeCase() { + $user = $this->drupalCreateUser(array( + 'view old datastream versions', + 'delete fedora objects and datastreams', + )); + $this->drupalLogin($user); + + $this->drupalGet("islandora/object/{$this->pid}/datastream/{$this->dsid}/version/6/delete"); + $this->assertResponse(404); + + $this->drupalGet("islandora/object/{$this->pid}/datastream/DC/version/0/delete"); + $this->assertResponse(404); + } +} diff --git a/tests/derivatives.test b/tests/derivatives.test new file mode 100644 index 00000000..13014880 --- /dev/null +++ b/tests/derivatives.test @@ -0,0 +1,296 @@ + 'Islandora Derivative Generation', + 'description' => 'Ensure that the derivative generation hooks return appropriate results.', + 'group' => 'Islandora', + ); + } + + /** + * Creates an admin user and a connection to a fedora repository. + * + * @see IslandoraWebTestCase::setUp() + */ + public function setUp() { + parent::setUp( + array( + 'islandora_derivatives_test', + ) + ); + $url = variable_get('islandora_base_url', 'http://localhost:8080/fedora'); + $this->connection = new RepositoryConnection($url, $this->admin->name, $this->admin->pass); + $this->connection->reuseConnection = TRUE; + $this->api = new FedoraApi($this->connection); + $this->cache = new SimpleCache(); + $this->repository = new FedoraRepository($this->api, $this->cache); + $this->pid = $this->randomName() . ":" . $this->randomName(); + } + + /** + * Free any objects/resources created for this test. + * + * @see IslandoraWebTestCase::tearDown() + */ + public function tearDown() { + $tuque = islandora_get_tuque_connection(); + parent::tearDown(); + } + + /** + * Tests that the islandora_islandora_object_ingested hook gets fired. + */ + public function testDerivativeOnIngest() { + global $_islandora_derivative_test_ingest_method; + $_islandora_derivative_test_ingest_method = 'modifyDatastream'; + $tuque = islandora_get_tuque_connection(); + $object = $tuque->repository->constructObject($this->pid); + $object->models = array( + 'some:cmodel', + ); + $dsid = 'OBJ'; + $ds = $object->constructDatastream($dsid); + $ds->label = 'Test'; + $ds->content = 'test'; + $object->ingestDatastream($ds); + $tuque->repository->ingestObject($object); + $this->assertDatastreams($object, array( + 'RELS-EXT', + 'DC', + 'OBJ', + 'DERIV', + 'NOSOURCE', + )); + $this->assertEqual('ingestDatastream', $_islandora_derivative_test_ingest_method, 'The expected ingest method is "ingestDatastream", got "' . $_islandora_derivative_test_ingest_method . '".'); + $this->assertEqual('test some string', $object['DERIV']->content, 'The expected content of the DERIV datastream is "test some string", got "' . $object['DERIV']->content . '".'); + $this->assertEqual('NOSOURCE', $object['NOSOURCE']->content, 'The expected content of the NOSOURCE datastream is "NOSOURCE", got "' . $object['NOSOURCE']->content . '".'); + + } + + /** + * Tests the ingest method when when forcing on existing datastreams. + */ + public function testDerivativeOnForceExistingDatastream() { + global $_islandora_derivative_test_ingest_method; + $_islandora_derivative_test_ingest_method = 'ingestDatastream'; + $object = $this->constructBaseObject(); + $object = $this->constructDERIVDatastream($object); + $this->constructNOSOURCEDatastream($object); + $islandora_object = islandora_object_load($this->pid); + islandora_do_derivatives($islandora_object, array( + 'force' => TRUE, + )); + $this->assertEqual('modifyDatastream', $_islandora_derivative_test_ingest_method, 'The expected ingest method is "modifyDatastream", got "' . $_islandora_derivative_test_ingest_method . '".'); + $this->assertEqual('FORCEFULLY APPENDING CONTENT TO test', $islandora_object['DERIV']->content, 'The expected content of the DERIV datastream is "FORCEFULLY APPENDING CONTENT TO test", got "' . $islandora_object['DERIV']->content . '".'); + } + + /** + * Tests the ingest method when forcing on non-existing datastreams. + */ + public function testDerivativeOnForceNonExistingDatastream() { + global $_islandora_derivative_test_ingest_method; + $_islandora_derivative_test_ingest_method = 'modifyDatastream'; + $this->constructBaseObject(); + $object = islandora_object_load($this->pid); + islandora_do_derivatives($object, array( + 'force' => TRUE, + )); + $this->assertEqual('ingestDatastream', $_islandora_derivative_test_ingest_method, 'The expected ingest method is "ingestDatastream", got "' . $_islandora_derivative_test_ingest_method . '".'); + $this->assertEqual('test some string', $object['DERIV']->content, 'The expected content of the DERIV datastream is "test some string", got "' . $object['DERIV']->content . '".'); + } + + /** + * Tests the islandora_datastream_modified hook when there are existing DSes. + */ + public function testDerivativeOnModifyExistingDatastream() { + global $_islandora_derivative_test_ingest_method; + $_islandora_derivative_test_ingest_method = 'ingestDatastream'; + $object = $this->constructBaseObject(); + $this->constructDERIVDatastream($object); + // Need to do this as Tuque caches. + $connection = islandora_get_tuque_connection(); + $connection->cache->resetCache(); + $islandora_object = islandora_object_load($this->pid); + $changed_content = 'islandora beast'; + $islandora_object['OBJ']->content = $changed_content; + $this->assertEqual('modifyDatastream', $_islandora_derivative_test_ingest_method, 'The expected ingest method is "modifyDatastream", got "' . $_islandora_derivative_test_ingest_method . '".'); + $this->assertEqual('FORCEFULLY APPENDING CONTENT TO ' . $changed_content, $islandora_object['DERIV']->content, 'The expected content of the DERIV datastream is "FORCEFULLY APPENDING CONTENT TO islandora beast", got "' . $islandora_object['DERIV']->content . '".'); + } + + /** + * Tests islandora_datastream_modified hook when there are no existing DSes. + */ + public function testDerivativeOnModifyNonExistingDatastream() { + global $_islandora_derivative_test_ingest_method; + $_islandora_derivative_test_ingest_method = 'modifyDatastream'; + $this->constructBaseObject(); + // Need to do this as Tuque caches. + $connection = islandora_get_tuque_connection(); + $connection->cache->resetCache(); + $islandora_object = islandora_object_load($this->pid); + $changed_content = 'islandora beast'; + $islandora_object['OBJ']->content = $changed_content; + $this->assertEqual('ingestDatastream', $_islandora_derivative_test_ingest_method, 'The expected ingest method is "ingestDatastream", got "' . $_islandora_derivative_test_ingest_method . '".'); + $this->assertEqual($changed_content . ' some string', $islandora_object['DERIV']->content, 'The expected content of the DERIV datastream is "islandora beast string", got "' . $islandora_object['DERIV']->content . '".'); + } + + /** + * Tests derivative hook filtering based upon source_dsid. + */ + public function testDerivativeFilteringOnSourceDSID() { + global $_islandora_derivative_test_derivative_functions; + $_islandora_derivative_test_derivative_functions = array(); + $this->constructBaseObject(); + $object = islandora_object_load($this->pid); + islandora_do_derivatives($object, array( + 'source_dsid' => 'OBJ', + )); + $this->assertEqual(1, count($_islandora_derivative_test_derivative_functions), 'Expected 1 derivative function for the source_dsid of "OBJ", got ' . count($_islandora_derivative_test_derivative_functions) . '.'); + $called_function = (string) reset($_islandora_derivative_test_derivative_functions); + $this->assertEqual('islandora_derivatives_test_create_deriv_datastream', $called_function, 'Expected derivative function is "islandora_derivatives_test_create_deriv_datastream", got "' . $called_function . '".'); + + // Reset the derivative functions array as we are going to use it again. + $_islandora_derivative_test_derivative_functions = array(); + islandora_do_derivatives($object, array( + 'source_dsid' => 'SOMEWEIRDDATASTREAM', + )); + $this->assertEqual(1, count($_islandora_derivative_test_derivative_functions), 'Expected 1 derivative function for the source_dsid of "SOMEWEIRDDATASTREAM", got ' . count($_islandora_derivative_test_derivative_functions) . '.'); + $called_function = (string) reset($_islandora_derivative_test_derivative_functions); + $this->assertEqual('islandora_derivatives_test_create_some_weird_datastream', $called_function, 'Expected derivative function is "islandora_derivatives_test_create_some_weird_datastream", got "' . $called_function . '".'); + } + + /** + * Tests that only functions were the source_dsid is NULL are fired. + */ + public function testNULLSourceDSID() { + global $_islandora_derivative_test_derivative_functions; + $_islandora_derivative_test_derivative_functions = array(); + $this->constructBaseObject(); + $object = islandora_object_load($this->pid); + islandora_do_derivatives($object, array( + 'source_dsid' => NULL, + )); + $this->assertDatastreams($object, array( + 'DC', + 'RELS-EXT', + 'OBJ', + 'NOSOURCE', + )); + $this->assertEqual(1, count($_islandora_derivative_test_derivative_functions), 'Expected 1 derivative function for the source_dsid of "NULL", got ' . count($_islandora_derivative_test_derivative_functions) . '.'); + $called_function = (string) reset($_islandora_derivative_test_derivative_functions); + $this->assertEqual('islandora_derivatives_test_create_nosource_datastream', $called_function, 'Expected derivative function is "islandora_derivatives_test_create_nosource_datastream", got "' . $called_function . '".'); + $this->assertEqual('NOSOURCE', $object['NOSOURCE']->content, 'The expected content of the NOSOURCE datastream is "NOSOURCE", got "' . $object['NOSOURCE']->content . '".'); + } + + /** + * Tests that when no source_dsid all derivative functions are called. + */ + public function testNoSourceDSIDNoForce() { + global $_islandora_derivative_test_derivative_functions; + $_islandora_derivative_test_derivative_functions = array(); + $this->constructBaseObject(); + $object = islandora_object_load($this->pid); + islandora_do_derivatives($object, array()); + $this->assertDatastreams($object, array( + 'DC', + 'RELS-EXT', + 'OBJ', + 'DERIV', + 'NOSOURCE', + )); + $this->assertEqual(3, count($_islandora_derivative_test_derivative_functions), 'Expected 3 derivative functions when there is no source_dsid, got ' . count($_islandora_derivative_test_derivative_functions) . '.'); + } + + /** + * Tests that when no source_dsid all derivative functions are called. + */ + public function testNoSourceDSIDForce() { + global $_islandora_derivative_test_derivative_functions; + $_islandora_derivative_test_derivative_functions = array(); + $this->constructBaseObject(); + $object = islandora_object_load($this->pid); + islandora_do_derivatives($object, array( + 'force' => TRUE, + )); + $this->assertDatastreams($object, array( + 'DC', + 'RELS-EXT', + 'OBJ', + 'DERIV', + 'NOSOURCE', + )); + $this->assertEqual(3, count($_islandora_derivative_test_derivative_functions), 'Expected 3 derivative functions when there is no source_dsid, got ' . count($_islandora_derivative_test_derivative_functions) . '.'); + } + + /** + * Helper function that will construct a base object. + */ + public function constructBaseObject() { + $object = $this->repository->constructObject($this->pid); + $object->models = array( + 'some:cmodel', + ); + $dsid = 'OBJ'; + $ds = $object->constructDatastream($dsid); + $ds->label = 'Test'; + $ds->content = 'test'; + $object->ingestDatastream($ds); + $this->repository->ingestObject($object); + return $object; + } + + /** + * Helper function to construct the DERIV datastream without firing hooks. + * + * @param AbstractObject $object + * An AbstractObject representing a FedoraObject. + * + * @return AbstractObject + * The modified AbstractObject. + */ + public function constructDERIVDatastream(AbstractObject $object) { + $dsid = 'DERIV'; + $ds = $object->constructDatastream($dsid); + $ds->label = 'Test'; + $ds->content = 'test some string'; + $object->ingestDatastream($ds); + return $object; + } + + /** + * Helper function to construct the NOSOURCE datastream without firing hooks. + * + * @param AbstractObject $object + * An AbstractObject representing a FedoraObject. + * + * @return AbstractObject + * The modified AbstractObject. + */ + public function constructNOSOURCEDatastream(AbstractObject $object) { + $dsid = 'NOSOURCE'; + $ds = $object->constructDatastream($dsid); + $ds->label = 'Test'; + $ds->content = 'NOSOURCE'; + $object->ingestDatastream($ds); + return $object; + } +} diff --git a/tests/fixtures/test.jpg b/tests/fixtures/test.jpg new file mode 100644 index 00000000..12f8398c Binary files /dev/null and b/tests/fixtures/test.jpg differ diff --git a/tests/hooked_access.test b/tests/hooked_access.test index 0039460e..894eee80 100644 --- a/tests/hooked_access.test +++ b/tests/hooked_access.test @@ -37,9 +37,9 @@ class IslandoraHookedAccessTestCase extends IslandoraWebTestCase { $this->objects = array( 'test:testAccessHook', ); - $this->op = FEDORA_VIEW_OBJECTS; - $this->other_op = FEDORA_INGEST; - $this->denied_op = FEDORA_PURGE; + $this->op = ISLANDORA_VIEW_OBJECTS; + $this->other_op = ISLANDORA_INGEST; + $this->denied_op = ISLANDORA_PURGE; $this->purgeTestObjects(); $this->dsid = 'asdf'; $this->createTestObjects(); diff --git a/tests/islandora_derivatives_test.info b/tests/islandora_derivatives_test.info new file mode 100644 index 00000000..338b39fe --- /dev/null +++ b/tests/islandora_derivatives_test.info @@ -0,0 +1,7 @@ +name = Islandora Derivatives Generation testing +description = Tests derivative generation hooks. Do not enable. +core = 7.x +package = Testing +hidden = TRUE +files[] = islandora_derivatives_test.module +dependencies[] = islandora diff --git a/tests/islandora_derivatives_test.module b/tests/islandora_derivatives_test.module new file mode 100644 index 00000000..80530296 --- /dev/null +++ b/tests/islandora_derivatives_test.module @@ -0,0 +1,202 @@ + 'OBJ', + 'destination_dsid' => 'DERIV', + 'weight' => '0', + 'function' => array( + 'islandora_derivatives_test_create_deriv_datastream', + ), + ), + array( + 'source_dsid' => 'SOMEWEIRDDATASTREAM', + 'destination_dsid' => 'STANLEY', + 'weight' => '-1', + 'function' => array( + 'islandora_derivatives_test_create_some_weird_datastream', + ), + ), + array( + 'source_dsid' => NULL, + 'destination_dsid' => 'NOSOURCE', + 'weight' => '-3', + 'function' => array( + 'islandora_derivatives_test_create_nosource_datastream', + ), + ), + ); +} + +/** + * Creates the DERIV datastream for use in testing. + * + * @param AbstractObject $object + * An AbstractObject representing a Fedora object. + * @param bool $force + * Whether or not derivative generation is to be forced. + * @return array + * An array detailing the success of the operation. + * + * @see hook_islandora_derivative() + */ +function islandora_derivatives_test_create_deriv_datastream(AbstractObject $object, $force = FALSE) { + global $_islandora_derivative_test_derivative_functions; + $_islandora_derivative_test_derivative_functions[] = 'islandora_derivatives_test_create_deriv_datastream'; + $return = ''; + if (!isset($object['DERIV']) || (isset($object['DERIV']) && $force === TRUE)) { + if ($force !== TRUE || !isset($object['DERIV'])) { + $deriv_string = $object['OBJ']->content . ' some string'; + } + else { + $deriv_string = "FORCEFULLY APPENDING CONTENT TO " . $object['OBJ']->content; + } + $added_successfully = islandora_derivatives_test_add_datastream($object, 'DERIV', $deriv_string); + if ($added_successfully !== TRUE) { + $return = islandora_derivatives_test_failed_adding($added_successfully); + } + else { + $return = array( + 'success' => TRUE, + 'messages' => array( + array( + 'message' => t('The DERIV datastream was added successfully for @pid!'), + 'message_sub' => array('@pid' => $object->id), + 'type' => 'dsm', + ), + ), + ); + } + return $return; + } +} + +/** + * Stub function that used only for datastream filtering counts. + * + * @param AbstractObject $object + * An AbstractObject representing a Fedora object. + * @param bool $force + * Whether the derivatives are being forcefully generated or not. + */ +function islandora_derivatives_test_create_some_weird_datastream(AbstractObject $object, $force = FALSE) { + global $_islandora_derivative_test_derivative_functions; + // Add to the global that we got to this function. + $_islandora_derivative_test_derivative_functions[] = 'islandora_derivatives_test_create_some_weird_datastream'; +} + +/** + * Creates the NOSOURCE datastream for use in testing. + * + * @param AbstractObject $object + * An AbstractObject representing a Fedora object. + * @param bool $force + * Whether or not derivative generation is to be forced. + * @return array + * An array detailing the success of the operation. + * + * @see hook_islandora_derivative() + */ +function islandora_derivatives_test_create_nosource_datastream(AbstractObject $object, $force = FALSE) { + global $_islandora_derivative_test_derivative_functions; + $_islandora_derivative_test_derivative_functions[] = 'islandora_derivatives_test_create_nosource_datastream'; + $return = ''; + if (!isset($object['NOSOURCE']) || (isset($object['NOSOURCE']) && $force === TRUE)) { + if ($force !== TRUE || !isset($object['NOSOURCE'])) { + $deriv_string = 'NOSOURCE'; + } + else { + $deriv_string = "FORCEFULLY APPENDING CONTENT TO " . $object['NOSOURCE']->content; + } + $added_successfully = islandora_derivatives_test_add_datastream($object, 'NOSOURCE', $deriv_string); + if ($added_successfully !== TRUE) { + $return = islandora_derivatives_test_failed_adding($added_successfully); + } + else { + $return = array( + 'success' => TRUE, + 'messages' => array( + array( + 'message' => t('The DERIV datastream was added successfully for @pid!'), + 'message_sub' => array('@pid' => $object->id), + 'type' => 'dsm', + ), + ), + ); + } + return $return; + } +} + +/** + * Helper function that adds/modifies the datastream to the object in testing. + * + * @param AbstractObject $object + * An AbstractObject representing a Fedora object. + * @param string $dsid + * The datastream id for which we are adding/modifying. + * @param string $deriv_string + * The content of the datastream we are adding. + * + * @return bool|string + * A bool if the operation was successfully, the error message otherwise. + */ +function islandora_derivatives_test_add_datastream(AbstractObject $object, $dsid, $deriv_string) { + global $_islandora_derivative_test_ingest_method; + try { + $ingest = !isset($object[$dsid]); + if ($ingest) { + $ds = $object->constructDatastream($dsid, 'M'); + $ds->label = $dsid; + } + else { + $ds = $object[$dsid]; + } + $ds->content = $deriv_string; + if ($ingest) { + $_islandora_derivative_test_ingest_method = 'ingestDatastream'; + $object->ingestDatastream($ds); + } + else { + $_islandora_derivative_test_ingest_method = 'modifyDatastream'; + } + return TRUE; + } + catch (exception $e) { + $message = $e->getMessage(); + return $message; + } +} + +/** + * Returns a message if we failed to add a derivative. + * + * @see hook_islandora_derivative() + * + * @param string $message + * The error message to be returned back. + * + * @return array + * An array describing the outcome of our failure. + */ +function islandora_derivatives_test_failed_adding($message) { + return array( + 'success' => FALSE, + 'messages' => array( + array( + 'message' => $message, + 'type' => 'watchdog', + 'severity' => WATCHDOG_ERROR, + ), + ), + ); +} diff --git a/tests/islandora_hooked_access_test.module b/tests/islandora_hooked_access_test.module index de417738..e21c0819 100644 --- a/tests/islandora_hooked_access_test.module +++ b/tests/islandora_hooked_access_test.module @@ -9,7 +9,7 @@ * Implements hook_islandora_object_access(). */ function islandora_hooked_access_test_islandora_object_access($op, $object, $user) { - if ($op == FEDORA_PURGE) { + if ($op == ISLANDORA_PURGE) { return FALSE; } if (isset($_SESSION['islandora_hooked_access_test']) && $_SESSION['islandora_hooked_access_test'] === func_get_args()) { @@ -23,6 +23,9 @@ function islandora_hooked_access_test_islandora_object_access($op, $object, $use * Implements hook_islandora_datastream_access(). */ function islandora_hooked_access_test_islandora_datastream_access($op, $datastream, $user) { + if ($op == FEDORA_PURGE) { + return FALSE; + } if (isset($_SESSION['islandora_hooked_access_test']) && $_SESSION['islandora_hooked_access_test'] === func_get_args()) { return TRUE; } diff --git a/tests/islandora_ingest_test.module b/tests/islandora_ingest_test.module index d414c717..8a6771d9 100644 --- a/tests/islandora_ingest_test.module +++ b/tests/islandora_ingest_test.module @@ -87,7 +87,11 @@ function islandora_ingest_test_test_testcmodel2_islandora_ingest_steps(array &$f * The Drupal form definition. */ function islandora_ingest_test_set_label_form(array $form, array &$form_state) { - $models = array('test:nomorestepscmodel', 'test:testcmodel', 'test:testcmodel2'); + $models = array( + 'test:nomorestepscmodel', + 'test:testcmodel', + 'test:testcmodel2', + ); $model = isset($form_state['values']['model']) ? $form_state['values']['model'] : reset($models); $shared_storage = &islandora_ingest_form_get_shared_storage($form_state); $shared_storage['models'] = array($model); @@ -139,8 +143,7 @@ function islandora_ingest_test_set_label_form_submit(array $form, array &$form_s * Test the First content model. */ function islandora_ingest_test_testcmodel_form(array $form, array &$form_state) { - return array( - ); + return array(); } /** @@ -154,8 +157,7 @@ function islandora_ingest_test_testcmodel_form_submit(array $form, array &$form_ * Test the second content model. */ function islandora_ingest_test_testcmodel2_form(array $form, array &$form_state) { - return array( - ); + return array(); } /** diff --git a/tests/islandora_manage_permissions.test b/tests/islandora_manage_permissions.test index 8fc1549f..e17d1501 100644 --- a/tests/islandora_manage_permissions.test +++ b/tests/islandora_manage_permissions.test @@ -33,16 +33,16 @@ class IslandoraPermissionsTestCase extends IslandoraWebTestCase { * Test manage permissions. */ public function testManagePermissions() { - // Test permission FEDORA_VIEW_OBJECTS. + // Test permission ISLANDORA_VIEW_OBJECTS. // Create a user with permission. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS)); + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS)); // Log the user in. $this->drupalLogin($user); $this->clickLink(t('Islandora Repository')); $this->assertNoLink('Manage', 'Manage tab is not on current page.'); - // Test permission FEDORA_VIEW_OBJECTS, FEDORA_MANAGE_PROPERTIES. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS, FEDORA_MANAGE_PROPERTIES)); + // Test permission ISLANDORA_VIEW_OBJECTS, ISLANDORA_MANAGE_PROPERTIES. + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_MANAGE_PROPERTIES)); $this->drupalLogin($user); $this->clickLink(t('Islandora Repository')); $this->assertLink('Manage', 0, 'Manage tab is on current page.'); @@ -51,8 +51,8 @@ class IslandoraPermissionsTestCase extends IslandoraWebTestCase { $this->assertNoLink('Datastreams', 'Datastreams tab is not on current page.'); $this->assertNoLink('Collection', 'Collection tab is not on current page.'); - // Test permission FEDORA_VIEW_OBJECTS, FEDORA_ADD_DS. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS, FEDORA_ADD_DS)); + // Test permission ISLANDORA_VIEW_OBJECTS, ISLANDORA_ADD_DS. + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_ADD_DS)); $this->drupalLogin($user); $this->clickLink(t('Islandora Repository')); $this->assertLink('Manage', 0, 'Manage tab is on current page.'); @@ -61,8 +61,8 @@ class IslandoraPermissionsTestCase extends IslandoraWebTestCase { $this->assertNoLink('Properties', 'Properties tab is not on current page.'); $this->assertNoLink('Collection', 'Collection tab is not on current page.'); - // Test permission FEDORA_VIEW_OBJECTS, FEDORA_METADATA_EDIT. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS, FEDORA_METADATA_EDIT)); + // Test permission ISLANDORA_VIEW_OBJECTS, ISLANDORA_METADATA_EDIT. + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_METADATA_EDIT)); $this->drupalLogin($user); $this->clickLink(t('Islandora Repository')); $this->assertLink('Manage', 0, 'Manage tab is on current page.'); @@ -71,8 +71,8 @@ class IslandoraPermissionsTestCase extends IslandoraWebTestCase { $this->assertNoLink('Properties', 'Properties tab is not on current page.'); $this->assertNoLink('Collection', 'Collection tab is not on current page.'); - // Test permission FEDORA_VIEW_OBJECTS, FEDORA_PURGE. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS, FEDORA_PURGE)); + // Test permission ISLANDORA_VIEW_OBJECTS, ISLANDORA_PURGE. + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_PURGE)); $this->drupalLogin($user); $this->clickLink(t('Islandora Repository')); $this->assertLink('Manage', 0, 'Manage tab is on current page.'); @@ -98,39 +98,39 @@ class IslandoraPermissionsTestCase extends IslandoraWebTestCase { $ret = islandora_user_access($object, array()); $this->assertFalse($ret, 'User access denied when no permissions are provided.'); // Test access with matching permission. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS)); - $ret = islandora_user_access($object, array(FEDORA_VIEW_OBJECTS), array(), TRUE, $user); + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS)); + $ret = islandora_user_access($object, array(ISLANDORA_VIEW_OBJECTS), array(), TRUE, $user); $this->assertTrue($ret, 'User access granted when permissions match.'); // Test access with matching permission but access any is FALSE. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS)); - $ret = islandora_user_access($object, array(FEDORA_VIEW_OBJECTS, FEDORA_PURGE), array(), FALSE, $user); + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS)); + $ret = islandora_user_access($object, array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_PURGE), array(), FALSE, $user); $this->assertFalse($ret, 'User access denied for matching permission but with access any set to FALSE.'); // Test access with non-matching permission. - $user = $this->drupalCreateUser(array(FEDORA_PURGE)); - $ret = islandora_user_access($object, array(FEDORA_VIEW_OBJECTS), array(), TRUE, $user); + $user = $this->drupalCreateUser(array(ISLANDORA_PURGE)); + $ret = islandora_user_access($object, array(ISLANDORA_VIEW_OBJECTS), array(), TRUE, $user); $this->assertFalse($ret, 'User access denied when permissions did not match.'); // Test access with both permissions and content model. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS)); + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS)); $model = $object->models; $model = reset($model); - $ret = islandora_user_access($object, array(FEDORA_VIEW_OBJECTS), array($model), TRUE, $user); + $ret = islandora_user_access($object, array(ISLANDORA_VIEW_OBJECTS), array($model), TRUE, $user); $this->assertTrue($ret, 'User access granted for matching permission and model.'); // Test access with matching permissions and non-matching content model. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS)); - $ret = islandora_user_access($object, array(FEDORA_VIEW_OBJECTS), array('islandora:obviouslyNotACModel'), TRUE, $user); + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS)); + $ret = islandora_user_access($object, array(ISLANDORA_VIEW_OBJECTS), array('islandora:obviouslyNotACModel'), TRUE, $user); $this->assertFalse($ret, 'User access denied for matching permission and non-matching model.'); // Test access with all matching permissions and one matching model but // access any is FALSE. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS, FEDORA_PURGE)); + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_PURGE)); $model = $object->models; $model = reset($model); - $ret = islandora_user_access($object, array(FEDORA_VIEW_OBJECTS, FEDORA_PURGE), array($model, 'islandora:obviouslyNotACModel'), FALSE, $user); + $ret = islandora_user_access($object, array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_PURGE), array($model, 'islandora:obviouslyNotACModel'), FALSE, $user); $this->assertFalse($ret, 'User access denied for all matching permissions and one matching model but with access any set to FALSE.'); - $ret = islandora_user_access($object, array(FEDORA_VIEW_OBJECTS, FEDORA_PURGE), array($model), FALSE, $user); + $ret = islandora_user_access($object, array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_PURGE), array($model), FALSE, $user); $this->assertTrue($ret, 'User access granted for all matching permissions and matching models with access any set to FALSE.'); // Test passing in a Datastream. - $user = $this->drupalCreateUser(array(FEDORA_VIEW_OBJECTS, FEDORA_PURGE)); - $ret = islandora_user_access($object['DC'], array(FEDORA_VIEW_OBJECTS), array(), TRUE, $user); + $user = $this->drupalCreateUser(array(ISLANDORA_VIEW_OBJECTS, ISLANDORA_PURGE)); + $ret = islandora_user_access($object['DC'], array(ISLANDORA_VIEW_OBJECTS), array(), TRUE, $user); $this->assertTrue($ret, 'User access granted for matching permissions, with a datastream given instead of an object.'); } } diff --git a/tests/islandora_web_test_case.inc b/tests/islandora_web_test_case.inc index de414c17..0ea96e8e 100644 --- a/tests/islandora_web_test_case.inc +++ b/tests/islandora_web_test_case.inc @@ -74,15 +74,22 @@ class IslandoraWebTestCase extends DrupalWebTestCase { $connection_info = Database::getConnectionInfo('default'); $drupal_filter_dom = new DomDocument(); $drupal_filter_dom->loadXML($this->originalDrupalFilterContent); - $drupal_filter_xpath = new DOMXPath($drupal_filter_dom); $server = $connection_info['default']['host']; $dbname = $connection_info['default']['database']; $user = $connection_info['default']['username']; $password = $connection_info['default']['password']; $port = $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'; $prefix = $connection_info['default']['prefix']['default']; - $results = $drupal_filter_xpath->query("/FilterDrupal_Connection/connection[@server='$server' and @dbname='$dbname' and @user='$user' and @password='$password' and @port='$port']/sql"); - $results->item(0)->nodeValue = "SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;"; + $filter_drupal_connection_node = $drupal_filter_dom->getElementsByTagName('FilterDrupal_Connection')->item(0); + $first_connection_node = $drupal_filter_dom->getElementsByTagName('connection')->item(0); + $connection_node = $filter_drupal_connection_node->insertBefore($drupal_filter_dom->createElement('connection'), $first_connection_node); + $connection_node->setAttributeNode(new DOMAttr('server', $server)); + $connection_node->setAttributeNode(new DOMAttr('dbname', $dbname)); + $connection_node->setAttributeNode(new DOMAttr('user', $user)); + $connection_node->setAttributeNode(new DOMAttr('password', $password)); + $connection_node->setAttributeNode(new DOMAttr('port', $port)); + $sql_node = $connection_node->appendChild(new DOMElement('sql')); + $sql_node->appendChild($drupal_filter_dom->createTextNode("SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;")); file_put_contents($this->configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); } @@ -157,25 +164,62 @@ class IslandoraWebTestCase extends DrupalWebTestCase { } /** - * Asserts that the given datastreams exist on the object. + * Asserts that the given datastreams exist correctly on the object. * * @param AbstractObject $object * The PID of the object * @param array $datastreams - * An array of strings containing datastream names + * An array of strings containing datastream names */ public function assertDatastreams($object, array $datastreams) { if (!is_object($object)) { - $this->fail("Failed. Object passed in is invalid."); - return; + $this->fail("Failed. Object passed in is invalid.", 'Islandora'); } + else { + foreach ($datastreams as $datastream) { + if (isset($object[$datastream])) { + $this->pass("Loaded datastream {$datastream} from PID {$object->id}.", 'Islandora'); + } + else { + $this->fail("Failed to load datastream {$datastream} from PID {$object->id}.", 'Islandora'); + } + } + } + } + /** + * Attempts to validate an array of datastreams, generally via binary checks. + * + * These functions exist in, and can be added to, datastream_validators.inc, + * which is found in this folder. + * + * $param AbstractObject $object + * The object to load datastreams from. + * $param array $datastreams + * An array of paired DSIDs, validate function names, and optional params. + */ + public function validateDatastreams($object, array $datastreams) { + if (!is_object($object)) { + $this->fail("Failed. Object passed in is invalid.", 'Islandora'); + } + module_load_include('inc', 'islandora', 'tests/datastream_validators'); foreach ($datastreams as $datastream) { - if (isset($object[$datastream])) { - $this->pass("Loaded datastream {$datastream} from PID {$object->id}"); - } - else { - $this->fail("Failed to load datastream {$datastream} from PID {$object->id}"); + if (isset($object[$datastream[0]])) { + $function = 'islandora_validate_' . $datastream[1] . '_datastream'; + if (function_exists($function)) { + if (isset($datastream[2])) { + $results = $function($object, $datastream[0], $datastream[2]); + } + else { + $results = $function($object, $datastream[0]); + } + foreach ($results as $result) { + $this->assertTrue($result[0], $result[1], 'Islandora'); + } + } + else { + $this->fail("No {$datastream[0]} validation function exists for the {$datastream[1]} datastream.", 'Islandora'); + } } } } @@ -199,7 +243,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { } } } - $this->fail("Failed to parse path : $path."); + $this->fail("Failed to parse path: $path."); return FALSE; } @@ -208,31 +252,119 @@ class IslandoraWebTestCase extends DrupalWebTestCase { * * @param string $pid * The PID of the collection to be deleted + * @param string $button + * The label of the first 'Delete' button */ - public function deleteObject($pid) { - $current_user = $this->loggedInUser; - $user = $this->drupalCreateUser(array( - 'manage object properties', - 'delete fedora objects and datastreams', - 'view fedora repository objects', - )); - - $this->drupalLogin($user); - + public function deleteObject($pid, $button = NULL) { $path = 'islandora/object/' . $pid . '/manage/properties'; $edit = array(); - $this->drupalPost($path, $edit, t('Delete')); + if (isset($button)) { + $this->drupalPost($path, $edit, $button); + } + else { + $object = islandora_object_load($pid); + $this->drupalPost($path, $edit, "Permanently remove '{$object->label}' from repository"); + } $this->drupalPost($this->url, $edit, t('Delete')); $object = islandora_object_load($pid); $this->drupalGet("islandora/object/$pid"); $this->assertResponse(404, "Object $pid successfully deleted."); + } + + /** + * Constructs and ingests a Fedora object and datastream(s) via tuque. + * + * All keys inside the parameter arrays for this function are optional. it + * can be run simply by calling $this->ingestConstructedObject();. + * + * @param array $properties + * An array containing object information using these keys: + * 'label' - The object label; randomized if not set. + * 'pid' - 'namespace:pid', or just 'namespace' to generate the suffix. + * 'models' - An array that can contain multiple content model PIDs. + * 'owner' - The object's owner. + * 'parent' - The PID of the parent collection. + * @param array $datastreams + * An array containing zero or more datastream arrays that use the keys: + * 'dsid' - the datastream ID; randomized if not set. + * 'path' - The path to the file to use; defaults to fixtures/test.jpg. + * 'control_group' - The single-letter control group identifier. + * 'mimetype' - The datastream's mimetype. + * + * @return bool|array + * FALSE if the object ingest failed, or the object array if successful. + */ + public function ingestConstructedObject(array $properties = array(), array $datastreams = array()) { + module_load_include('inc', 'islandora', 'includes/tuque'); + $tuque = new IslandoraTuque(); + $repository = $tuque->repository; + if (!isset($properties['pid'])) { + $properties['pid'] = "islandora"; + } + $object = $repository->constructObject($properties['pid']); + + // Set the object properties before ingesting it. + if (isset($properties['label'])) { + $object->label = $properties['label']; + } + else { + $properties['label'] = $this->randomName(16); + $object->label = $properties['label']; + } + + if (isset($properties['owner'])) { + $object->owner = $properties['owner']; + } + + if (isset($properties['models']) && is_array($properties['models'])) { + foreach ($properties['models'] as $model) { + $object->relationships->add(FEDORA_MODEL_URI, 'hasModel', $model); + } + } + elseif (isset($properties['models']) && !is_array($properties['models'])) { + $this->fail(t("'models' key of properties variable is not an array. Content model(s) will not be set."), 'Islandora'); + } - if ($current_user) { - $this->drupalLogin($current_user); + $repository->ingestObject($object); + if (!$object) { + $this->fail(t("Failed to ingest object."), 'Islandora'); + return FALSE; } else { - $this->drupalLogout(); + $this->pass(t("Ingested object %object", array('%object' => $object->id)), 'Islandora'); } + + // Chuck in some datastreams. + if (!empty($datastreams)) { + foreach ($datastreams as $datastream) { + if (!isset($datastream['dsid'])) { + $datastream['dsid'] = $this->randomName(8); + } + if (!isset($datastream['path'])) { + $datastream['path'] = drupal_get_path('module', 'islandora') . '/tests/fixtures/test.jpg'; + } + if (!isset($datastream['control_group'])) { + $new_datastream = $object->constructDatastream($datastream['dsid']); + } + else { + $new_datastream = $object->constructDatastream($datastream['dsid'], $datastream['control_group']); + } + $new_datastream->label = $datastream['dsid']; + if (isset($datastream['mimetype'])) { + $new_datastream->mimetype = $datastream['mimetype']; + } + $new_datastream->setContentFromFile($datastream['path']); + $object->ingestDatastream($new_datastream); + } + } + + // Add a parent relationship, if necessary. + if (isset($properties['parent'])) { + $object->relationships->add(FEDORA_RELS_EXT_URI, 'isMemberOfCollection', $properties['parent']); + } + + return $object; } + } diff --git a/tests/scripts/travis_setup.sh b/tests/scripts/travis_setup.sh index 19c7db78..3a52bb75 100755 --- a/tests/scripts/travis_setup.sh +++ b/tests/scripts/travis_setup.sh @@ -9,6 +9,7 @@ git clone git://github.com/Islandora/tuque.git git clone -b $FEDORA_VERSION git://github.com/Islandora/islandora_tomcat.git cd islandora_tomcat export CATALINA_HOME='.' +export JAVA_OPTS="-Xms1024m -Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -Djavax.net.ssl.trustStore=$CATALINA_HOME/fedora/server/truststore -Djavax.net.ssl.trustStorePassword=tomcat" ./bin/startup.sh cd $HOME pear upgrade --force Console_Getopt @@ -18,13 +19,13 @@ pear channel-discover pear.drush.org pear channel-discover pear.drush.org pear channel-discover pear.phpqatools.org pear channel-discover pear.netpirates.net -pear install pear/PHP_CodeSniffer +pear install pear/PHP_CodeSniffer-1.4.8 pear install pear.phpunit.de/phpcpd -pear install drush/drush +pear install drush/drush-5.9.0 phpenv rehash drush dl --yes drupal cd drupal-* -drush si standard --db-url=mysql://drupal:drupal@localhost/drupal --yes +drush si minimal --db-url=mysql://drupal:drupal@localhost/drupal --yes drush runserver --php-cgi=$HOME/.phpenv/shims/php-cgi localhost:8081 &>/dev/null & ln -s $ISLANDORA_DIR sites/all/modules/islandora mv sites/all/modules/islandora/tests/travis.test_config.ini sites/all/modules/islandora/tests/test_config.ini diff --git a/theme/islandora-dublin-core-description.tpl.php b/theme/islandora-dublin-core-description.tpl.php new file mode 100644 index 00000000..2ff54cdc --- /dev/null +++ b/theme/islandora-dublin-core-description.tpl.php @@ -0,0 +1,20 @@ + + diff --git a/theme/islandora-dublin-core-display.tpl.php b/theme/islandora-dublin-core-display.tpl.php new file mode 100644 index 00000000..2c2a02ee --- /dev/null +++ b/theme/islandora-dublin-core-display.tpl.php @@ -0,0 +1,33 @@ + +
> + +
+ +
+
diff --git a/theme/islandora-object-img-print.tpl.php b/theme/islandora-object-img-print.tpl.php new file mode 100644 index 00000000..3ff21455 --- /dev/null +++ b/theme/islandora-object-img-print.tpl.php @@ -0,0 +1,18 @@ + + +
+ +
+ diff --git a/theme/islandora-object-print.tpl.php b/theme/islandora-object-print.tpl.php new file mode 100644 index 00000000..53fdc529 --- /dev/null +++ b/theme/islandora-object-print.tpl.php @@ -0,0 +1,14 @@ + +
+
+ +
+ +
diff --git a/theme/islandora-object.tpl.php b/theme/islandora-object.tpl.php index 13b40ba0..6fa306c3 100644 --- a/theme/islandora-object.tpl.php +++ b/theme/islandora-object.tpl.php @@ -60,6 +60,7 @@ ?>

+
diff --git a/theme/theme.inc b/theme/theme.inc index f07f95d4..6a80fbb6 100644 --- a/theme/theme.inc +++ b/theme/theme.inc @@ -15,61 +15,73 @@ function islandora_preprocess_islandora_default_edit(array &$variables) { $variables['islandora_editmetadata_url'] = $base_url . '/islandora/edit_form/' . $islandora_object->id; module_load_include('inc', 'islandora', 'includes/datastream'); module_load_include('inc', 'islandora', 'includes/utilities'); - $header = array( - array('data' => t('ID')), - array('data' => t('Label')), - array('data' => t('Type')), - array('data' => t('Mime type')), - array('data' => t('Size')), - array('data' => t('Operations'), 'colspan' => '3'), - ); + $header = array(); + $header[] = array('data' => t('ID')); + $header[] = array('data' => t('Label')); + $header[] = array('data' => t('Type')); + $header[] = array('data' => t('Mime type')); + $header[] = array('data' => t('Size')); + if (user_access(ISLANDORA_VIEW_DATASTREAM_HISTORY)) { + $header[] = array('data' => t('Versions')); + } + + $header[] = array('data' => t('Operations'), 'colspan' => '3'); + $table_attributes = array('class' => array('manage-datastreams')); $rows = array(); foreach ($islandora_object as $ds) { - $rows[] = array( - array( - 'class' => 'datastream-id', - 'data' => theme('islandora_datastream_view_link', array( - 'datastream' => $ds, - )), - ), - array( - 'class' => 'datastream-label', - 'data' => $ds->label, - ), - array( - 'class' => 'datastream-control', - 'data' => islandora_control_group_to_human_readable($ds->controlGroup), - ), - array( - 'class' => 'datastream-mime', - 'data' => $ds->mimeType, - ), - array( - 'class' => 'datastream-size', - 'data' => islandora_datastream_get_human_readable_size($ds), - ), - array( - 'class' => 'datastream-download', - 'data' => theme('islandora_datastream_download_link', array( - 'datastream' => $ds, - )), - ), - array( - 'class' => 'datstream-edit', - 'data' => theme('islandora_datastream_edit_link', array( - 'datastream' => $ds, - )), - ), - array( - 'class' => 'datastream-delete', - 'data' => theme('islandora_datastream_delete_link', array( + $row = array(); + $row[] = array( + 'class' => 'datastream-id', + 'data' => theme('islandora_datastream_view_link', array( + 'datastream' => $ds, + )), + ); + $row[] = array( + 'class' => 'datastream-label', + 'data' => filter_xss($ds->label), + ); + $row[] = array( + 'class' => 'datastream-control', + 'data' => islandora_control_group_to_human_readable($ds->controlGroup), + ); + $row[] = array( + 'class' => 'datastream-mime', + 'data' => filter_xss($ds->mimeType), + ); + $row[] = array( + 'class' => 'datastream-size', + 'data' => islandora_datastream_get_human_readable_size($ds), + ); + if (user_access(ISLANDORA_VIEW_DATASTREAM_HISTORY)) { + $row[] = array( + 'class' => 'datastream-versions', + 'data' => theme('islandora_datastream_version_link', array( 'datastream' => $ds, )), - ), + ); + } + $row[] = array( + 'class' => 'datastream-download', + 'data' => theme('islandora_datastream_download_link', array( + 'datastream' => $ds, + )), ); + $row[] = array( + 'class' => 'datstream-edit', + 'data' => theme('islandora_datastream_edit_link', array( + 'datastream' => $ds, + )), + ); + $row[] = array( + 'class' => 'datastream-delete', + 'data' => theme('islandora_datastream_delete_link', array( + 'datastream' => $ds, + )), + ); + $rows[] = $row; } - $caption = $islandora_object->label . ' - ' . $islandora_object->id; + $caption = filter_xss($islandora_object->label) . ' - ' . $islandora_object->id; $table = array( 'colgroups' => NULL, 'sticky' => TRUE, @@ -103,7 +115,7 @@ function islandora_preprocess_islandora_default(&$variables) { $download_path = islandora_datastream_get_url($ds, 'download'); $datastreams[$id]['id'] = $id; $datastreams[$id]['label'] = $label; - $datastreams[$id]['label_link'] = islandora_datastream_access(FEDORA_VIEW_OBJECTS, $ds) ? + $datastreams[$id]['label_link'] = islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $ds) ? l($label, $download_path) : $label; $datastreams[$id]['download_url'] = $download_path; @@ -118,14 +130,14 @@ function islandora_preprocess_islandora_default(&$variables) { } $variables['datastreams'] = $datastreams; // Objects in fcrepo4 don't always contain a DC datastream. - if (isset($islandora_object['DC']) && islandora_datastream_access(FEDORA_VIEW_OBJECTS, $islandora_object['DC'])) { + if (isset($islandora_object['DC']) && islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $islandora_object['DC'])) { $dc_object = DublinCore::importFromXMLString($islandora_object['DC']->content); $dc_array = $dc_object->asArray(); } $variables['dc_array'] = isset($dc_array) ? $dc_array : array(); $variables['islandora_dublin_core'] = isset($dc_object) ? $dc_object : NULL; $variables['islandora_object_label'] = $islandora_object->label; - if (isset($islandora_object['TN']) && islandora_datastream_access(FEDORA_VIEW_OBJECTS, $islandora_object['TN'])) { + if (isset($islandora_object['TN']) && islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $islandora_object['TN'])) { $variables['islandora_thumbnail_url'] = url("islandora/object/{$islandora_object->id}/datastream/TN/view"); } } @@ -207,11 +219,11 @@ function islandora_preprocess_islandora_objects(array &$variables) { $o = islandora_object_load($o); $url = "islandora/object/{$o->id}"; $link_options = array('html' => TRUE, 'attributes' => array('title' => $o->label)); - $img = islandora_datastream_access(FEDORA_VIEW_OBJECTS, $o['TN']) ? + $img = islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $o['TN']) ? theme('image', array('path' => url("$url/datastream/TN/view"), 'attributes' => array())) : ''; $description = NULL; - if (isset($o['DC']) && islandora_datastream_access(FEDORA_VIEW_OBJECTS, $o['DC'])) { + if (isset($o['DC']) && islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $o['DC'])) { $dc = DublinCore::importFromXMLString($o['DC']->content); if ($dc) { $dc = $dc->asArray(); @@ -239,13 +251,16 @@ function islandora_preprocess_islandora_objects(array &$variables) { * @param array $vars * An array containing: * - datastream: An AbstractDatastream for which to generate a download link. + * + * @return string + * Markup containing the download url if the user has access, empty otherwise. */ function theme_islandora_datastream_download_link(array $vars) { $datastream = $vars['datastream']; module_load_include('inc', 'islandora', 'includes/utilities'); $label = t('download'); - return islandora_datastream_access(FEDORA_VIEW_OBJECTS, $datastream) ? + return islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $datastream) ? l($label, islandora_datastream_get_url($datastream, 'download')) : ''; } @@ -256,15 +271,36 @@ function theme_islandora_datastream_download_link(array $vars) { * @param array $vars * An array containing: * - datastream: An AbstractDatastream for which to generate a view link. + * - label: (Optional) The label for the link. + * - version: (Optional) The version of the datstream to link to. + * + * @return string + * Markup containing the link to the datastream or the label if inaccessible. */ function theme_islandora_datastream_view_link(array $vars) { $datastream = $vars['datastream']; module_load_include('inc', 'islandora', 'includes/utilities'); - $label = check_plain($datastream->id); - return islandora_datastream_access(FEDORA_VIEW_OBJECTS, $datastream) ? - l($label, islandora_datastream_get_url($datastream, 'view')) : - $label; + if ($vars['label'] === NULL) { + $label = check_plain($datastream->id); + } + else { + $label = check_plain($vars['label']); + } + + if ($vars['version'] === NULL) { + $perm = ISLANDORA_VIEW_OBJECTS; + } + else { + $perm = ISLANDORA_VIEW_DATASTREAM_HISTORY; + } + + if (islandora_datastream_access($perm, $datastream)) { + return l($label, islandora_datastream_get_url($datastream, 'view', $vars['version'])); + } + else { + return $label; + } } /** @@ -273,17 +309,68 @@ function theme_islandora_datastream_view_link(array $vars) { * @param array $vars * An array containing: * - datastream: An AbstractDatastream for which to generate a delete link. + * - version: (optional) the version of the datastream to delete. + * + * @return string + * Markup containing the url to delete a datastream, or empty if inaccessible. */ function theme_islandora_datastream_delete_link(array $vars) { $datastream = $vars['datastream']; $datastreams = module_invoke_all('islandora_undeletable_datastreams', $datastream->parent->models); - $can_delete = !in_array($datastream->id, $datastreams) && islandora_datastream_access(FEDORA_PURGE, $datastream); + $can_delete = !in_array($datastream->id, $datastreams) && islandora_datastream_access(ISLANDORA_PURGE, $datastream); + + if ($vars['version'] !== NULL) { + if (count($datastream) == 1) { + $can_delete = FALSE; + } + $link = "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/version/{$vars['version']}/delete"; + } + else { + $link = "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/delete"; + } - return $can_delete ? - l(t('delete'), "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/delete") : + if ($can_delete) { + return l(t('delete'), $link); + } + else { ''; + } +} + +/** + * Renders a link to allow replacing of a datatream. + * + * @param array $vars + * An array containing: + * - datastream: An AbstractDatastream for which to generate a revert link. + * - version: (optional) the version of the datastream to revert. + * + * @return string + * Markup containing the url to delete a datastream, or empty if inaccessible. + */ +function theme_islandora_datastream_revert_link(array $vars) { + $datastream = $vars['datastream']; + + $can_revert = islandora_datastream_access(ISLANDORA_REVERT_DATASTREAM, $datastream); + + if ($vars['version'] !== NULL) { + if (count($datastream) == 1) { + $can_revert = FALSE; + } + $link = "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/version/{$vars['version']}/revert"; + } + else { + $can_revert = FALSE; + } + + if ($can_revert) { + return l(t('revert'), $link); + } + else { + return ''; + } } /** @@ -292,15 +379,81 @@ function theme_islandora_datastream_delete_link(array $vars) { * @param array $vars * An array containing: * - datastream: An AbstractDatastream for which to generate a edit link. + * + * @return string + * Markup containing the url to edit a datastream, or empty if inaccessible. */ function theme_islandora_datastream_edit_link(array $vars) { $datastream = $vars['datastream']; $edit_registry = module_invoke_all('islandora_edit_datastream_registry', $datastream->parent, $datastream); - $can_edit = count($edit_registry) > 0 && islandora_datastream_access(FEDORA_METADATA_EDIT, $datastream); + $can_edit = count($edit_registry) > 0 && islandora_datastream_access(ISLANDORA_METADATA_EDIT, $datastream); return $can_edit ? l(t('edit'), "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/edit") : ''; } + +/** + * Renders a link to take you to the datastream versions page. + * + * @param array $vars + * An array containing: + * - datastream: An AbstractDatastream to generate the version link from. + * + * @return string + * Markup. + */ +function theme_islandora_datastream_version_link(array $vars) { + $datastream = $vars['datastream']; + module_load_include('inc', 'islandora', 'includes/utilities'); + + $see_history = islandora_datastream_access(ISLANDORA_VIEW_DATASTREAM_HISTORY, $datastream); + + if ($see_history) { + if ($datastream->versionable) { + return l(count($datastream), "islandora/object/{$datastream->parent->id}/datastream/{$datastream->id}/version"); + } + else { + return t('Not Versioned'); + } + } + else { + return ''; + } +} + +/** + * Implements hook_preprocess(). + */ +function islandora_preprocess_islandora_dublin_core_display(array &$variables) { + $islandora_object = $variables['islandora_object']; + if (islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $islandora_object['DC'])) { + try { + $dc = $islandora_object['DC']->content; + $dc_object = DublinCore::importFromXMLString($dc); + } + catch (Exception $e) { + drupal_set_message(t('Error retrieving object %s %t', array('%s' => $islandora_object->id, '%t' => $e->getMessage())), 'error', FALSE); + } + } + $variables['dc_array'] = isset($dc_object) ? $dc_object->asArray() : array(); +} + +/** + * Implements hook_preprocess(). + */ +function islandora_preprocess_islandora_dublin_core_description(array &$variables) { + $islandora_object = $variables['islandora_object']; + if (islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $islandora_object['DC'])) { + try { + $dc = $islandora_object['DC']->content; + $dc_object = DublinCore::importFromXMLString($dc); + } + catch (Exception $e) { + drupal_set_message(t('Error retrieving object %s %t', array('%s' => $islandora_object->id, '%t' => $e->getMessage())), 'error', FALSE); + } + } + $variables['dc_array'] = isset($dc_object) ? $dc_object->asArray() : array(); +}