diff --git a/includes/datastream.inc b/includes/datastream.inc index da55dcd5..585fa2f5 100644 --- a/includes/datastream.inc +++ b/includes/datastream.inc @@ -30,6 +30,13 @@ function islandora_download_datastream(AbstractDatastream $datastream) { * The version of the datastream to display */ 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]; diff --git a/includes/datastream.version.inc b/includes/datastream.version.inc index 1e7c0830..507a182f 100644 --- a/includes/datastream.version.inc +++ b/includes/datastream.version.inc @@ -18,7 +18,7 @@ function islandora_datastream_version_table($datastream) { $header[] = array('data' => t('Size')); $header[] = array('data' => t('Label')); $header[] = array('data' => t('Mime type')); - $header[] = array('data' => t('Operations')); + $header[] = array('data' => t('Operations'), 'colspan' => '2'); $rows = array(); foreach ($datastream as $version => $datastream_version) { @@ -50,6 +50,13 @@ function islandora_datastream_version_table($datastream) { 'version' => $version, )), ); + $row[] = array( + 'class' => 'datastream-revert', + 'data' => theme('islandora_datastream_revert_link', array( + 'datastream' => $datastream, + 'version' => $version, + )), + ); $rows[] = $row; } @@ -125,3 +132,79 @@ function islandora_delete_datastream_version_form_submit(array $form, array &$fo $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"; +} diff --git a/includes/derivatives.inc b/includes/derivatives.inc index 4a422535..fbf4b9e7 100644 --- a/includes/derivatives.inc +++ b/includes/derivatives.inc @@ -98,7 +98,7 @@ function islandora_derivative_logging(array $logging_results) { // 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', $message['message'], isset($message['message_sub']) ? $message['message_sub'] : array(), isset($message['severity']) ? $message['severity'] : WATCHDOG_NOTICE); + 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/islandora.api.php b/islandora.api.php index e0cc86b6..2e6ef831 100644 --- a/islandora.api.php +++ b/islandora.api.php @@ -477,10 +477,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) { @@ -515,10 +516,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) { diff --git a/islandora.module b/islandora.module index eb05211e..9df17892 100644 --- a/islandora.module +++ b/islandora.module @@ -35,6 +35,8 @@ 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'); @@ -278,6 +280,16 @@ function islandora_menu() { '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', @@ -402,6 +414,10 @@ function islandora_theme() { 'file' => 'theme/theme.inc', '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( @@ -454,6 +470,10 @@ function islandora_permission() { '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.'), + ), ); } diff --git a/tests/islandora_web_test_case.inc b/tests/islandora_web_test_case.inc index baf7a8ee..be3b3b7e 100644 --- a/tests/islandora_web_test_case.inc +++ b/tests/islandora_web_test_case.inc @@ -168,16 +168,45 @@ class IslandoraWebTestCase extends DrupalWebTestCase { if (!is_object($object)) { $this->fail("Failed. Object passed in is invalid.", 'Islandora'); } - - 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'); + 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'); + } } } + } + /** + * Asserts that an object's given datastreams are common-type image files. + * + * Uses PHPGD to run the assertion check. This means that only certain kinds + * of image files can be checked. Please check the documentation for the PHPGD + * imagecreatefromstring() function to determine what filetypes are valid. + * + * @param AbstractObject $object + * The PID of the object. + * @param array $datastreams + * An array of datastreams to check. + */ + public function assertImageDatastreams($object, array $datastreams) { + if (!is_object($object)) { + $this->fail("Failed. Object passed in is invalid.", 'Islandora'); + } + else { + foreach ($datastreams as $datastream) { + $datastream_string = $object[$datastream]->content; + if (!imagecreatefromstring($datastream_string)) { + $this->fail("Image datastream {$datastream} is either invalid or corrupt.", 'Islandora'); + } + else { + $this->pass("Image datastream {$datastream} is valid.", 'Islandora'); + } + } + } } /** diff --git a/theme/theme.inc b/theme/theme.inc index 10e1cd29..895ec8fe 100644 --- a/theme/theme.inc +++ b/theme/theme.inc @@ -339,6 +339,40 @@ function theme_islandora_datastream_delete_link(array $vars) { } } +/** + * 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 ''; + } +} + /** * Renders a link to allow editing of a datatream. *