Browse Source

resolved merge conflicts

pull/386/head
Alan Stanley 11 years ago
parent
commit
20a529821e
  1. 6
      includes/admin.form.inc
  2. 2
      includes/breadcrumb.inc
  3. 157
      includes/datastream.inc
  4. 4
      includes/solution_packs.inc
  5. 18
      islandora.api.php
  6. 1
      islandora.info
  7. 46
      islandora.module
  8. 160
      tests/datastream_cache.test

6
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'), '#description' => t('The PID of the Root Collection Object'),
'#required' => TRUE, '#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( 'islandora_namespace' => array(
'#type' => 'fieldset', '#type' => 'fieldset',

2
includes/breadcrumb.inc

@ -27,6 +27,8 @@
function islandora_get_breadcrumbs($object) { function islandora_get_breadcrumbs($object) {
$breadcrumbs = islandora_get_breadcrumbs_recursive($object->id, $object->repository); $breadcrumbs = islandora_get_breadcrumbs_recursive($object->id, $object->repository);
array_pop($breadcrumbs); array_pop($breadcrumbs);
$context = 'islandora';
drupal_alter('islandora_breadcrumbs', $breadcrumbs, $context);
return $breadcrumbs; return $breadcrumbs;
} }

157
includes/datastream.inc

@ -46,8 +46,6 @@ function islandora_view_datastream(AbstractDatastream $datastream, $download = F
} }
} }
header_remove('Cache-Control');
header_remove('Expires');
header('Content-type: ' . $datastream->mimetype); header('Content-type: ' . $datastream->mimetype);
if ($datastream->controlGroup == 'M' || $datastream->controlGroup == 'X') { if ($datastream->controlGroup == 'M' || $datastream->controlGroup == 'X') {
header('Content-length: ' . $datastream->size); header('Content-length: ' . $datastream->size);
@ -59,13 +57,164 @@ function islandora_view_datastream(AbstractDatastream $datastream, $download = F
$filename = $datastream->label . '.' . $extension; $filename = $datastream->label . '.' . $extension;
header("Content-Disposition: attachment; filename=\"$filename\""); 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); drupal_page_is_cacheable(FALSE);
// Try not to load the file into PHP memory! // Try not to load the file into PHP memory!
ob_end_flush(); // Close and flush ALL the output buffers!
$datastream->getContent('php://output'); while (@ob_end_flush()) {
};
// New content needed.
if ($cache_check === 200) {
$datastream->getContent('php://output');
}
exit(); 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. * Get the human readable size of the given datastream.
* *

4
includes/solution_packs.inc

@ -745,7 +745,9 @@ function islandora_get_viewer($params = NULL, $variable_id = NULL, $fedora_objec
$viewer_id = islandora_get_viewer_id($variable_id); $viewer_id = islandora_get_viewer_id($variable_id);
if ($viewer_id AND $params !== NULL) { if ($viewer_id AND $params !== NULL) {
$callback = islandora_get_viewer_callback($viewer_id); $callback = islandora_get_viewer_callback($viewer_id);
return $callback($params, $fedora_object); if (function_exists($callback)) {
return $callback($params, $fedora_object);
}
} }
} }
return FALSE; return FALSE;

18
islandora.api.php

@ -86,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 * @param AbstractObject $object
* A Tuque FedoraObject * A Tuque FedoraObject
@ -98,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 * Content models PIDs have colons and hyphens changed to underscores, to
* create the hook name. * create the hook name.
@ -113,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 * @param AbstractObject $object
* A Tuque FedoraObject * A Tuque FedoraObject
@ -650,6 +650,7 @@ function hook_CMODEL_PID_islandora_derivative() {
} }
/** /**
* Retrieves PIDS of related objects for property updating. * Retrieves PIDS of related objects for property updating.
* *
* @param AbstractObject $object * @param AbstractObject $object
@ -663,3 +664,14 @@ function hook_islandora_update_related_objects_properties(AbstractObject $object
} }
return $pids_to_return; 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) {
}

1
islandora.info

@ -18,5 +18,6 @@ files[] = tests/ingest.test
files[] = tests/hooked_access.test files[] = tests/hooked_access.test
files[] = tests/islandora_manage_permissions.test files[] = tests/islandora_manage_permissions.test
files[] = tests/datastream_versions.test files[] = tests/datastream_versions.test
files[] = tests/datastream_cache.test
files[] = tests/derivatives.test files[] = tests/derivatives.test
php = 5.3 php = 5.3

46
islandora.module

@ -122,12 +122,21 @@ function islandora_menu() {
'access callback' => 'islandora_object_access_callback', 'access callback' => 'islandora_object_access_callback',
'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2), 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2),
); );
$items['islandora/object/%islandora_object/print'] = array( $items['islandora/object/%islandora_object/print_object'] = array(
'page callback' => 'islandora_printer_object', 'page callback' => 'islandora_printer_object',
'page arguments' => array(2), 'page arguments' => array(2),
'type' => MENU_NORMAL_ITEM, 'type' => MENU_NORMAL_ITEM,
'access callback' => 'islandora_object_access_callback', '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), 'access arguments' => array(ISLANDORA_VIEW_OBJECTS, 2),
'load arguments' => array(2),
); );
$items['islandora/object/%islandora_object/view'] = array( $items['islandora/object/%islandora_object/view'] = array(
'title' => 'View', 'title' => 'View',
@ -154,13 +163,11 @@ function islandora_menu() {
ISLANDORA_INGEST, ISLANDORA_INGEST,
), 2), ), 2),
); );
$items['islandora/object/%islandora_object/manage/overview'] = array( $items['islandora/object/%islandora_object/manage/overview'] = array(
'title' => 'Overview', 'title' => 'Overview',
'type' => MENU_DEFAULT_LOCAL_TASK, 'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -20, 'weight' => -20,
); );
$items['islandora/object/%islandora_object/manage/datastreams'] = array( $items['islandora/object/%islandora_object/manage/datastreams'] = array(
'title' => 'Datastreams', 'title' => 'Datastreams',
'type' => MENU_LOCAL_TASK, 'type' => MENU_LOCAL_TASK,
@ -175,7 +182,6 @@ function islandora_menu() {
), 2), ), 2),
'weight' => -10, 'weight' => -10,
); );
$items['islandora/object/%islandora_object/manage/properties'] = array( $items['islandora/object/%islandora_object/manage/properties'] = array(
'title' => 'Properties', 'title' => 'Properties',
'page callback' => 'drupal_get_form', 'page callback' => 'drupal_get_form',
@ -374,8 +380,13 @@ function islandora_theme() {
'file' => 'includes/solution_packs.inc', 'file' => 'includes/solution_packs.inc',
'render element' => 'form', 'render element' => 'form',
), ),
// Print object view. // Print used by the clipper.
'islandora_object_print' => array( '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', 'file' => 'theme/theme.inc',
'template' => 'theme/islandora-object-print', 'template' => 'theme/islandora-object-print',
'variables' => array( 'variables' => array(
@ -477,6 +488,22 @@ function islandora_permission() {
); );
} }
/**
* 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(). * Implements hook_forms().
*/ */
@ -848,7 +875,12 @@ function islandora_view_object(AbstractObject $object) {
drupal_add_js(array( drupal_add_js(array(
'islandora' => array( 'islandora' => array(
<<<<<<< HEAD
'print_link' => 'islandora/object/' . $object->id . '/print')), array('type' => 'setting')); 'print_link' => 'islandora/object/' . $object->id . '/print')), array('type' => 'setting'));
=======
'print_link' => 'islandora/object/' . $object->id . '/print_object')),
array('type' => 'setting'));
>>>>>>> dd50626677e1acbc5fe69daf25d13cfa65dae7af
drupal_add_js($path . '/js/add_print.js'); drupal_add_js($path . '/js/add_print.js');
drupal_set_title($object->label); drupal_set_title($object->label);
@ -977,7 +1009,7 @@ function islandora_default_islandora_printer_object($object, $alter) {
} }
$variables = isset($dc_object) ? $dc_object->asArray() : array(); $variables = isset($dc_object) ? $dc_object->asArray() : array();
$output = theme('islandora_object_print', array( $output = theme('islandora_object_print_object', array(
'object' => $object, 'object' => $object,
'dc_array' => $variables, 'dc_array' => $variables,
'islandora_content' => $alter)); 'islandora_content' => $alter));

160
tests/datastream_cache.test

@ -0,0 +1,160 @@
<?php
/**
* @file
* Tests to verify the cache headers we provide.
*/
class IslandoraDatastreamCacheTestCase extends IslandoraWebTestCase {
/**
* Gets info to display to describe this test.
*
* @see IslandoraWebTestCase::getInfo()
*/
public static function getInfo() {
return array(
'name' => '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);
}
}
Loading…
Cancel
Save