diff --git a/.travis.yml b/.travis.yml
index 660fb448..84b226d0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,9 +6,14 @@ branches:
only:
- /^7.x/
env:
- - FEDORA_VERSION="3.5"
- - FEDORA_VERSION="3.6.2"
- - FEDORA_VERSION="3.7.0"
+ matrix:
+ - FEDORA_VERSION="3.5"
+ - FEDORA_VERSION="3.6.2"
+ - FEDORA_VERSION="3.7.0"
+ global:
+ # This key is unique to the Islandora/islandora repository; logging will
+ # fail on forked repositories unless a new unique key is generated for them.
+ - secure: "nTv2Zb/qKlECK+xE5ahbfXI9ZZbf2ZMd796q7oPlTxUwvu6nomHnUOjJATl6tq2cj23PyJ89Jlgl5cMZ5h0QMUzYpN5hPyY6oCJxWgFamFaE3bv5E/rBd1f6WVTJW7S4UKn8Mr9R2PrX+ZxQZGVIigAfR8VfhQuP8PcuO5eMLBk="
before_install:
- export ISLANDORA_DIR=$TRAVIS_BUILD_DIR
- $TRAVIS_BUILD_DIR/tests/scripts/travis_setup.sh
@@ -19,3 +24,5 @@ script:
- drush coder-review --reviews=production,security,style,i18n,potx,sniffer islandora
- phpcpd --names *.module,*.inc,*.test sites/all/modules/islandora
- drush test-run --uri=http://localhost:8081 Islandora
+after_failure:
+ - $ISLANDORA_DIR/tests/scripts/travis_after_failure.sh
diff --git a/includes/authtokens.inc b/includes/authtokens.inc
index 36e8689f..d5d98117 100644
--- a/includes/authtokens.inc
+++ b/includes/authtokens.inc
@@ -36,12 +36,7 @@ define('ISLANDORA_AUTHTOKEN_TOKEN_TIMEOUT', 300);
function islandora_get_object_token($pid, $dsid, $uses = 1) {
global $user;
$time = time();
- // The function mt_rand is not considered cryptographically secure
- // and openssl_rando_pseudo_bytes() is only available in PHP > 5.3.
- // We might be safe in this case because mt_rand should never be using
- // the same seed, but this is still more secure.
- $token = hash("sha256", mt_rand() . $time);
-
+ $token = bin2hex(drupal_random_bytes(32));
$id = db_insert("islandora_authtokens")->fields(
array(
'token' => $token,
diff --git a/includes/derivatives.inc b/includes/derivatives.inc
index d25789cc..47ec4fad 100644
--- a/includes/derivatives.inc
+++ b/includes/derivatives.inc
@@ -4,6 +4,45 @@
* Defines functions used when constructing derivatives.
*/
+/**
+ * Decides which derivative function to call and runs it.
+ *
+ * @param AbstractObject $object
+ * The object to run the derivative function for.
+ * @param string $dsid
+ * The DSID to run the derivative function for.
+ */
+function islandora_run_derivatives(AbstractObject $object, $dsid) {
+ $batch_array = batch_get();
+ if (empty($batch_array)) {
+ $logging_results = islandora_do_derivatives($object, array(
+ 'source_dsid' => $dsid,
+ ));
+ islandora_derivative_logging($logging_results);
+ }
+ else {
+ batch_set(
+ // Title won't show for batch in a batch.
+ array(
+ 'init_message' => t('Preparing derivatives for @label', array('@label' => $object->label)),
+ 'error_message' => t('An error occured creating derivatives.'),
+ 'progress_message' => t(
+ 'Creating derivatives for @label
Time elapsed: @elapsed
+ Estimated time remaining @estimate.',
+ array('@label' => $object->label)
+ ),
+ 'file' => drupal_get_path('module', 'islandora') . '/includes/regenerate_derivatives.form.inc',
+ 'operations' => islandora_do_batch_derivatives(
+ $object,
+ array(
+ 'source_dsid' => $dsid,
+ )
+ ),
+ )
+ );
+ }
+}
+
/**
* Kicks off derivative functions based upon hooks and conditions.
*
@@ -49,7 +88,7 @@ function islandora_do_derivatives(AbstractObject $object, array $options) {
}
foreach ($hook['function'] as $function) {
if (function_exists($function)) {
- $logging = call_user_func($function, $object, $options['force']);
+ $logging = call_user_func($function, $object, $options['force'], $hook);
if (!empty($logging)) {
$results[] = $logging;
}
@@ -142,11 +181,15 @@ function islandora_do_batch_derivatives(AbstractObject $object, array $options)
$file = $hook['file'];
}
foreach ($hook['function'] as $function) {
- $operations[] = array('islandora_derivative_perform_batch_operation', array(
- $function,
- $file,
- $object->id,
- $options['force']),
+ $operations[] = array(
+ 'islandora_derivative_perform_batch_operation',
+ array(
+ $function,
+ $file,
+ $object->id,
+ $options['force'],
+ $hook,
+ ),
);
}
}
diff --git a/includes/ingest.form.inc b/includes/ingest.form.inc
index 89376f7d..f7979063 100644
--- a/includes/ingest.form.inc
+++ b/includes/ingest.form.inc
@@ -467,12 +467,14 @@ function islandora_ingest_form_undo_consecutive_callback_steps(array $form, arra
* 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'];
+ if (isset($step['undo_function'])) {
+ $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);
}
- call_user_func_array($step['undo_function']['function'], $args);
}
/**
@@ -769,7 +771,7 @@ 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;
+ $set_redirect = isset($form_state['redirect']) ? FALSE : TRUE;
foreach ($form_state['islandora']['objects'] as &$object) {
try {
islandora_add_object($object);
diff --git a/includes/regenerate_derivatives.form.inc b/includes/regenerate_derivatives.form.inc
index 7fd8d059..9a7a097e 100644
--- a/includes/regenerate_derivatives.form.inc
+++ b/includes/regenerate_derivatives.form.inc
@@ -142,15 +142,17 @@ function islandora_regenerate_datastream_derivative_batch(AbstractDatastream $da
* The pid of the object we are performing.
* @param bool $force
* Whether we are forcing derivative regeneration or not.
+ * @param array $hook
+ * The derivative definition.
* @param array $context
* The context of the current batch operation.
*/
-function islandora_derivative_perform_batch_operation($function, $file, $pid, $force, &$context) {
+function islandora_derivative_perform_batch_operation($function, $file, $pid, $force, $hook, &$context) {
if ($file) {
require_once $file;
}
if (function_exists($function)) {
- $logging = call_user_func($function, islandora_object_load($pid), $force);
+ $logging = call_user_func($function, islandora_object_load($pid), $force, $hook);
if (!empty($logging)) {
$context['results']['logging'][] = $logging;
}
diff --git a/includes/solution_packs.inc b/includes/solution_packs.inc
index e07e76e7..47a8f23c 100644
--- a/includes/solution_packs.inc
+++ b/includes/solution_packs.inc
@@ -256,7 +256,7 @@ function islandora_solution_pack_form_submit(array $form, array &$form_state) {
* the batch.
* @param array $not_checked
* The object that will bot be install.
- *
+ *
* @return array
* An array defining a batch which can be passed on to batch_set().
*/
@@ -367,7 +367,7 @@ function islandora_install_solution_pack($module, $op = 'install', $force = FALS
$t = get_t();
// Some general replacements.
- $admin_link = l($t('Solution Pack admin'), 'admin/islandora/solution_packs');
+ $admin_link = l($t('Solution Pack admin'), 'admin/islandora/solution_pack_config');
$config_link = l($t('Islandora configuration'), 'admin/islandora/configure');
$t_params = array(
'@module' => $module,
@@ -712,9 +712,8 @@ function islandora_viewers_form($variable_id = NULL, $mimetype = NULL, $model =
* given, than any viewer that supports either the give $mimetype or $model will
* be listed.
*
- * @param string $mimetype
- * Specify a mimetype to return only viewers that support this certain
- * mimetype.
+ * @param array $mimetype
+ * List of mimetypes that the viewer supports.
* @param string $content_model
* Specify a content model to return only viewers that support the content
* model.
@@ -722,14 +721,19 @@ function islandora_viewers_form($variable_id = NULL, $mimetype = NULL, $model =
* @return array
* Viewer definitions, or FALSE if none are found.
*/
-function islandora_get_viewers($mimetype = NULL, $content_model = NULL) {
+function islandora_get_viewers($mimetype = array(), $content_model = NULL) {
$viewers = array();
$defined_viewers = module_invoke_all('islandora_viewer_info');
+
+ if (!is_array($mimetype)) {
+ $mimetype = array($mimetype);
+ }
+
// Filter viewers by MIME type.
foreach ($defined_viewers as $key => $value) {
$value['mimetype'] = isset($value['mimetype']) ? $value['mimetype'] : array();
$value['model'] = isset($value['model']) ? $value['model'] : array();
- if (in_array($mimetype, $value['mimetype']) OR in_array($content_model, $value['model'])) {
+ if (array_intersect($mimetype, $value['mimetype']) OR in_array($content_model, $value['model'])) {
$viewers[$key] = $value;
}
}
diff --git a/includes/tuque_wrapper.inc b/includes/tuque_wrapper.inc
index d30af147..573d7c62 100644
--- a/includes/tuque_wrapper.inc
+++ b/includes/tuque_wrapper.inc
@@ -115,6 +115,10 @@ class IslandoraFedoraRepository extends FedoraRepository {
foreach ($object as $dsid => $datastream) {
islandora_invoke_datastream_hooks(ISLANDORA_DATASTREAM_INGESTED_HOOK, $object->models, $dsid, $object, $datastream);
}
+ // Fire of event if rules is enabled.
+ if (module_exists('rules')) {
+ rules_invoke_event('islandora_object_ingested', $object);
+ }
return $ret;
}
catch (Exception $e) {
diff --git a/islandora.api.php b/islandora.api.php
index e8b45ba7..159806cb 100644
--- a/islandora.api.php
+++ b/islandora.api.php
@@ -423,13 +423,13 @@ function hook_islandora_undeletable_datastreams(array $models) {
* - form_id: The form building function to call to get the form structure
* for this step.
* - args: An array of arguments to pass to the form building function.
- * Required "callback" type specific parameters:
- * - do_function: An associate array including:
+ * "Callback" type specific parameters:
+ * - do_function: A required associative 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:
+ * - undo_function: An optional associative 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.
@@ -627,22 +627,23 @@ function hook_CMODEL_PID_islandora_overview_object_alter(AbstractObject &$object
* - 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.
+ * from or NULL if it's the object itself. Does not impact function
+ * ordering.
* - destination_dsid: (Optional) String of the datastream id that is being
- * created. To be used in the UI.
+ * created. To be used in the UI. Does not impact function ordering.
* - 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)
+ * module_name_derivative_creation_function($object, $force = FALSE, $hook)
* These functions must return an array in the structure of:
* - success: Bool denoting whether the operation was successful.
* - messages: An array structure containing zero or more array's with the
* following fields:
* - message: A string passed through t() describing the
* outcome of the operation.
- * - message_sub: (Optional) Substitutions to be passed along to t() or
+ * - message_sub: (Optional) A substitution array as acceptable by t() or
* watchdog.
* - type: A string denoting whether the output is to be
* drupal_set_messaged (dsm) or watchdogged (watchdog).
diff --git a/islandora.info b/islandora.info
index f47d610c..75147143 100644
--- a/islandora.info
+++ b/islandora.info
@@ -22,5 +22,9 @@ files[] = tests/islandora_manage_permissions.test
files[] = tests/datastream_versions.test
files[] = tests/datastream_cache.test
files[] = tests/derivatives.test
+<<<<<<< HEAD
files[] = tests/islandora_manage_temp_file.test
+=======
+files[] = tests/datastream_validator_tests.test
+>>>>>>> 6c3fcb0381318857b6c2aa7f9059f939ab7ecef5
php = 5.3
diff --git a/islandora.module b/islandora.module
index 9e7809eb..21c902c2 100644
--- a/islandora.module
+++ b/islandora.module
@@ -109,13 +109,35 @@ function islandora_menu() {
'file' => 'includes/metadata.inc',
'access arguments' => array('administer site configuration'),
);
- $items['admin/islandora/solution_packs'] = array(
- 'title' => 'Solution packs',
+ $items['admin/islandora/solution_pack_config'] = array(
+ 'title' => 'Solution pack configuration',
+ 'description' => 'Configure Islandora solution packs.',
+ 'access callback' => 'islandora_find_package',
+ 'access arguments' => array('Islandora Solution Packs'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ $items['admin/islandora/islandora_viewers'] = array(
+ 'title' => 'Islandora Viewers',
+ 'description' => 'Configure custom viewers.',
+ 'access callback' => 'islandora_find_package',
+ 'access arguments' => array('Islandora Viewers'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ $items['admin/islandora/tools'] = array(
+ 'title' => 'Islandora Utility Modules',
+ 'description' => 'Configure Islandora utility modules.',
+ 'access callback' => 'islandora_find_package',
+ 'access arguments' => array('Islandora Tools'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ $items['admin/islandora/solution_pack_config/solution_packs'] = array(
+ 'title' => 'Solution packs required objects',
'description' => 'Install content models and collections required by installed solution packs.',
'page callback' => 'islandora_solution_packs_admin',
'access arguments' => array(ISLANDORA_ADD_DS),
'file' => 'includes/solution_packs.inc',
'type' => MENU_NORMAL_ITEM,
+ 'weight' => -1,
);
$items['islandora'] = array(
'title' => 'Islandora Repository',
@@ -1531,6 +1553,11 @@ function islandora_entity_property_info() {
'label' => t('Content Models'),
'description' => t('The list of content models which the object has.'),
);
+ $p['createdDate'] = array(
+ 'type' => 'text',
+ 'label' => t('Created Date'),
+ 'description' => t('When the object was created.'),
+ );
return $info;
}
@@ -1695,10 +1722,7 @@ function islandora_islandora_basic_collection_get_query_filters() {
*/
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);
+ islandora_run_derivatives($object, NULL);
}
/**
@@ -1709,10 +1733,7 @@ function islandora_islandora_object_ingested(AbstractObject $object) {
*/
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);
+ islandora_run_derivatives($object, $datastream->id);
}
/**
@@ -1825,9 +1846,9 @@ function islandora_repair_drupal_filter() {
}
}
- /**
- * Implements hook_islandora_metadata_display_info().
- */
+/**
+ * Implements hook_islandora_metadata_display_info().
+ */
function islandora_islandora_metadata_display_info() {
return array(
'dublin_core' => array(
@@ -1853,7 +1874,8 @@ function islandora_islandora_datastream_access($op, AbstractDatastream $datastre
$hooks = islandora_invoke_hook_list(ISLANDORA_DERVIATIVE_CREATION_HOOK, $object->models, array($object));
$hooks = islandora_filter_derivatives($hooks, array('force' => TRUE), $object);
foreach ($hooks as $hook) {
- if ($hook['destination_dsid'] == $datastream->id && islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $object[$hook['source_dsid']], $user)) {
+ if ($hook['destination_dsid'] == $datastream->id &&
+ (is_null($hook['source_dsid']) || islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $object[$hook['source_dsid']], $user))) {
$applicable_hook = TRUE;
break;
}
@@ -1864,3 +1886,24 @@ function islandora_islandora_datastream_access($op, AbstractDatastream $datastre
}
return $result;
}
+
+/**
+ * Access for submenu items.
+ *
+ * @param string $package_name
+ * Name of the package
+ *
+ * @return bool
+ * Access granted
+ */
+function islandora_find_package($package_name) {
+ $results = system_get_info('module');
+ $found = FALSE;
+ foreach ($results as $name => $values) {
+ if ($values['package'] == $package_name) {
+ $found = TRUE;
+ break;
+ }
+ }
+ return $found && user_access('administer site configuration');
+}
diff --git a/islandora.rules.inc b/islandora.rules.inc
index 4045b1b9..02f5c822 100644
--- a/islandora.rules.inc
+++ b/islandora.rules.inc
@@ -5,11 +5,30 @@
* Does rule type stuff,
*/
+/**
+ * Implements hook_rules_event_info().
+ */
+function islandora_rules_event_info() {
+ return array(
+ 'islandora_object_ingested' => array(
+ 'group' => t('Islandora'),
+ 'label' => t('Object ingested'),
+ 'variables' => array(
+ 'object' => array(
+ 'type' => 'islandora_object',
+ 'label' => t('The ingested object'),
+ 'description' => t('A Tuque object for the ingested Fedora object, as an entity.'),
+ ),
+ ),
+ ),
+ );
+}
+
/**
* Helper function to get reused "parameter" array.
*/
function islandora_rules_relationship_parameter_array() {
- $to_return = array(
+ return array(
'subject' => array(
'type' => 'islandora_object',
'label' => t('Subject'),
@@ -39,8 +58,6 @@ function islandora_rules_relationship_parameter_array() {
'default value' => 0,
),
);
-
- return $to_return;
}
/**
diff --git a/islandora.rules_defaults.inc b/islandora.rules_defaults.inc
new file mode 100644
index 00000000..17f83b63
--- /dev/null
+++ b/islandora.rules_defaults.inc
@@ -0,0 +1,26 @@
+label = 'E-mail admin';
+ $rule->active = FALSE;
+ $rule
+ ->event('islandora_object_ingested')
+ ->action(
+ 'mail',
+ array(
+ 'to' => '[site:mail]',
+ 'subject' => '[[site:name]] "[object:label]" has been ingested',
+ 'message' => '[object:label] has been ingested as [object:id].',
+ )
+ );
+ return array('islandora_object_ingested_notify_admin' => $rule);
+}
diff --git a/tests/README.md b/tests/README.md
index c4a0ae1a..3918b117 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -1,4 +1,43 @@
-You can define your own configurations specific to your enviroment by copying
+OVERVIEW
+********
+
+You can define your own configurations specific to your environment by copying
default.test_config.ini to test_config.ini, making your changes in the copied
file. These test need write access to the system's $FEDORA_HOME/server/config
directory as well as the filter-drupal.xml file.
+
+DATASTREAM VALIDATION TESTS
+***************************
+
+The datastream validator included in the Islandora testing suite is able to
+generate tests procedurally based on the files in the folder
+'fixtures/datastream_validator_files'. By default, this folder is empty.
+The unit tests for the validator pull the name of the file (before the
+extension) and use that to instantiate the correct ______DatastreamValidator
+class to test that file against (e.g. Image.jpg spins up an instance of the
+ImageDatastreamValidator class and checks the results).
+
+You can test against multiple different encodings of the same filetype by giving
+each file a different set of extensions, e.g. MP3.vbr.mp3 and MP3.sbr.mp3 both
+test against the MP3 datastream validator, even though both are encoded
+differently.
+
+For classes that require the third parameter (e.g. the TextDatastreamValidator),
+place an additional name.extension.ini file in the datastream_validator_files
+folder (e.g. the Text.txt would be paired with Text.txt.ini). This .ini file
+should be structured like a PHP .ini file (e.g. according to the format used by
+http://php.net/parse_ini_file).The generated test will parse the .ini
+file as an array and pass it on to the third parameter.
+
+The following prefixes are currently available for use:
+
+- Image (jpg, png, gif, and other filetypes recognized by PHPGD)
+- TIFF
+- JP2
+- PDF
+- Text (requires a configured .ini)
+- WAV
+- MP3
+- MP4
+- OGG (asserts OGG video; use an .ini with an 'audio' key to test audio only)
+- MKV
diff --git a/tests/datastream_validator_tests.test b/tests/datastream_validator_tests.test
new file mode 100644
index 00000000..79e1df7f
--- /dev/null
+++ b/tests/datastream_validator_tests.test
@@ -0,0 +1,277 @@
+addResult(TRUE, 'yay you did it', $this->getAssertionCall());
+ }
+
+ /**
+ * Assertion that adds a FALSE result.
+ */
+ protected function assertSomethingFailed() {
+ $this->addResult(FALSE, 'boo you failed', $this->getAssertionCall());
+ }
+}
+
+/**
+ * Tests the ability of DatastreamValidators to produce correct results.
+ */
+class DatastreamValidatorResultTestCase extends IslandoraWebTestCase {
+
+ /**
+ * Returns the info for this test case.
+ *
+ * @see DrupalWebTestCase::getInfo()
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Datastream Validator Result Tests',
+ 'description' => 'Unit tests for datastream validation result functionality.',
+ 'group' => 'Islandora',
+ );
+ }
+
+ /**
+ * Sets up the test.
+ *
+ * @see DrupalWebTestCase::setUp()
+ */
+ public function setUp() {
+ parent::setUp();
+ $user = $this->drupalCreateUser(array_keys(module_invoke_all('permission')));
+ $this->drupalLogin($user);
+ $object = $this->ingestConstructedObject();
+ $validator = new TestDatastreamValidator($object, 'DC');
+ $this->validator_results = $validator->getResults();
+ }
+
+ /**
+ * Generates a generic DatastreamValidatorResult and grabs its properties.
+ */
+ public function testDatastreamValidatorResult() {
+ $result = new DatastreamValidatorResult(TRUE, 'true', array());
+ $this->assertEqual($result->getMessage(), 'true', "Result message generated correctly.", 'Islandora');
+ $this->assertEqual($result->getType(), TRUE, "Result type generated correctly.", 'Islandora');
+ $this->assertEqual($result->getCaller(), array(), "Result caller generated correctly.", 'Islandora');
+ }
+
+ /**
+ * Gets the results of TestDatastreamValidator and confirms only 2 exist.
+ */
+ public function testTestDatastreamValidatorResultCount() {
+ $this->assertTrue(isset($this->validator_results[0]), "First of two expected results found.", 'Islandora');
+ $this->assertTrue(isset($this->validator_results[1]), "Second of two expected results found.", 'Islandora');
+ $this->assertFalse(isset($this->validator_results[2]), "No other unexpected results found.", 'Islandora');
+ }
+
+ /**
+ * Confirms the messages from TestDatastreamValidator.
+ */
+ public function testTestDatastreamValidatorMessages() {
+ if (isset($this->validator_results[0]) && isset($this->validator_results[1])) {
+ $this->assertEqual($this->validator_results[0]->getMessage(), 'yay you did it', "Appropriate pass message returned.", 'Islandora');
+ $this->assertEqual($this->validator_results[1]->getMessage(), 'boo you failed', "Appropriate fail message returned.", 'Islandora');
+ }
+ }
+
+ /**
+ * Confirms the types from TestDatastreamValidator.
+ */
+ public function testTestDatastreamValidatorTypes() {
+ if (isset($this->validator_results[0]) && isset($this->validator_results[1])) {
+ $this->assertEqual($this->validator_results[0]->getType(), TRUE, "Appropriate pass type of TRUE returned.", 'Islandora');
+ $this->assertEqual($this->validator_results[1]->getType(), FALSE, "Appropriate fail type of FALSE returned.", 'Islandora');
+ }
+ }
+
+ /**
+ * Confirms the useful information from TestDatastreamValidator.
+ */
+ public function testTestDatastreamValidatorCallers() {
+ if (isset($this->validator_results[0]) && isset($this->validator_results[1])) {
+ // Grab the callers.
+ $first_caller = $this->validator_results[0]->getCaller();
+ $second_caller = $this->validator_results[1]->getCaller();
+
+ // Assert the 'file' key.
+ $this->assertTrue(substr($first_caller['file'], -48) === '/islandora/tests/datastream_validator_tests.test', "Appropriate pass caller file returned.", 'Islandora');
+ $this->assertTrue(substr($first_caller['file'], -48) === substr($second_caller['file'], -48), "Fail caller file matches the pass caller file.", 'Islandora');
+ $this->assertTrue($first_caller['function'] === 'TestDatastreamValidator->assertSomethingSuccessfully()', "Correct pass caller function returned (actual: {$first_caller['function']}; expected: TestDatastreamValidator->assertSomethingSuccessfully()).", 'Islandora');
+ $this->assertTrue($second_caller['function'] === 'TestDatastreamValidator->assertSomethingFailed()', "Correct fail caller function returned (actual: {$second_caller['function']}; expected: TestDatastreamValidator->assertSomethingFailed()).", 'Islandora');
+ $this->assertTrue($first_caller['line'] === 18, "Correct pass line number returned (actual: {$first_caller['line']}; expected: 9).", 'Islandora');
+ $this->assertTrue($second_caller['line'] === 25, "Correct fail line number returned (actual: {$second_caller['line']}; expected: 13).", 'Islandora');
+ }
+ }
+}
+
+/**
+ * Procedurally generated tests for DatastreamValidators.
+ */
+class PrefixDatastreamValidatorTestCase extends IslandoraWebTestCase {
+
+ /**
+ * The path to the datastream validator files.
+ *
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * Returns the info for this test case.
+ *
+ * @see DrupalWebTestCase::getInfo()
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Datastream File Validation Tests',
+ 'description' => 'Tests each file in the islandora/tests/fixtures/datastream_validator_files folder against the appropriate DatastreamValidator class (see the README.md in the tests folder for details).',
+ 'group' => 'Islandora',
+ );
+ }
+
+ /**
+ * Sets up the test.
+ *
+ * @see DrupalWebTestCase::setUp()
+ */
+ public function setUp() {
+ parent::setUp();
+ $this->path = DRUPAL_ROOT . drupal_get_path('module', 'islandora') . "/tests/fixtures/datastream_validator_files/";
+ }
+
+ /**
+ * Confirms that a DatastreamValidator class exists for given filename.
+ *
+ * @param string $filename
+ * The file to grab the DatastreamValidator prefix from.
+ *
+ * @return bool
+ * TRUE if such a class exists; FALSE otherwise.
+ */
+ protected function confirmValidatorClass($filename) {
+ $prefix = $this->getPrefix($filename);
+ if (!class_exists("{$prefix}DatastreamValidator")) {
+ $this->fail("No such DatastreamValidator exists for the prefix $prefix (filename: $filename).");
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Confirms that a file exists at the given path.
+ *
+ * Bundled with lovely return values and fail messages.
+ *
+ * @param string $path
+ * The path to the file.
+ *
+ * @return bool
+ * TRUE if the file exists, FALSE otherwise.
+ */
+ protected function confirmValidatorFile($path) {
+ if (!file_exists($path)) {
+ $this->fail("No such file exists at path $path.");
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Gets the intended prefix from a filename.
+ *
+ * Uses the portion of the filename before the first period.
+ *
+ * @param string $filename
+ * The filename to get the prefix for.
+ *
+ * @return string
+ * The intended prefix.
+ */
+ protected function getPrefix($filename) {
+ return array_shift(explode('.', $filename));
+ }
+
+ /**
+ * Create an object with a datastream generated from the given filename.
+ *
+ * @param string $filename
+ * The filename to use when adding a datastream.
+ *
+ * @return IslandoraFedoraObject
+ * The created object.
+ */
+ protected function createObjectWithDatastream($filename) {
+ $prefix = $this->getPrefix($filename);
+ if (!$this->confirmValidatorClass($filename) && !$this->confirmValidatorFile($this->path . $filename)) {
+ return FALSE;
+ }
+
+ $datastreams = array(array(
+ 'dsid' => $prefix,
+ 'path' => $this->path . $filename,
+ 'control_group' => 'M',
+ ),
+ );
+ $object = $this->ingestConstructedObject(array(), $datastreams);
+ return $object;
+ }
+
+ /**
+ * Procedurally test each file in $this->path against a datastream validator.
+ *
+ * Check the README.md in this folder for details.
+ */
+ public function testDatastreamValidators() {
+
+ // Grab everything in the validator files folder except for .ini files.
+ $files = array_intersect(glob("{$this->path}/*"), glob("{$this->path}/*.ini"));
+
+ foreach ($files as $file) {
+ $prefix = $this->getPrefix($file);
+
+ // If createObjectWithDatastream fails, we don't want to continue. We'll
+ // task it with returning fail messages rather than do so here.
+ $object = $this->createObjectWithDatastream($file);
+ if ($object !== FALSE) {
+ // Generate an appropriate validator.
+ $validator_name = "{$prefix}DatastreamValidator";
+ // If the file is paired with an .ini, use it as the third param.
+ if (file_exists("{$this->path}$file.ini")) {
+ $validator = new $validator_name($object, $prefix, parse_ini_file("{$this->path}$file.ini"));
+ }
+ else {
+ $validator = new $validator_name($object, $prefix);
+ }
+
+ // Get the results, check for fails.
+ $results = $validator->getResults();
+ $fails = FALSE;
+ foreach ($results as $result) {
+ if (!$result->getType()) {
+ $fails = TRUE;
+ $caller = $result->getCaller();
+ $this->fail("Failed to validate the test file $file against the assertion {$caller['function']}.");
+ }
+ }
+
+ // If there were no fails, say that the file passed validation.
+ if (!$fails) {
+ $this->pass("Test file $file validated successfully.");
+ }
+ }
+ }
+ }
+}
diff --git a/tests/datastream_validators.inc b/tests/datastream_validators.inc
index 4c69788b..151ec676 100644
--- a/tests/datastream_validators.inc
+++ b/tests/datastream_validators.inc
@@ -1,59 +1,9 @@
message = $message;
+ $this->caller = $caller;
+ $this->type = $type;
+ }
+
+ /**
+ * Get the message for this result.
+ *
+ * @return string
+ * The message for this result.
+ */
+ public function getMessage() {
+ return $this->message;
+ }
+
+ /**
+ * Get the caller for this result.
+ *
+ * @return string
+ * The caller for this result.
+ */
+ public function getCaller() {
+ return $this->caller;
+ }
+
+ /**
+ * Get the type of result.
+ *
+ * @return bool
+ * The type of pass (TRUE for pass, FALSE for fail).
+ */
+ public function getType() {
+ return $this->type;
+ }
+}
+
+/**
+ * Abstraction for datastream validators.
+ *
+ * Classes extended from DatastreamValidator don't require much to be useful.
+ * They accept an IslandoraFedoraObject and a DSID to perform assertions on;
+ * all you have to do is place a series of functions inside the extended class
+ * using the naming convention assertThing(); each of these functions should
+ * ideally assert one thing and one thing only (for simplicity's sake), and
+ * should generate either a pass or a fail message by calling addPass() or
+ * addFail(). That's it, really; they don't have to return any values, as
+ * addPass() and addFail() just add messages to the overall pass/fail array.
+ *
+ * As long as you use those rules and naming conventions, all the magic is done
+ * when you instantiate the new datastream validator object.
+ *
+ * The IslandoraWebTestCase::assertDatastreams() function accepts paired DSIDs
+ * and datastream validator names in order to do the rest of the work. It grabs
+ * all the test results using getPasses() and getFails() and transforms those
+ * into something that DrupalWebTestCase can use.
+ */
+abstract class DatastreamValidator {
+
+ /**
+ * The IslandoraFedoraObject containing the datastream to test.
+ *
+ * @var IslandoraFedoraObject
+ */
+ public $object;
+
+ /**
+ * The DSID of the string to test.
+ *
+ * @var string
+ */
+ public $datastream;
+
+ /**
+ * The content of the datastream.
+ *
+ * @var string[]
+ */
+ public $datastreamContent;
+
+ /**
+ * An array of DatastreamValidatorResults.
+ *
+ * These should be generated using $this->addResult.
+ *
+ * @var DatastreamValidatorResult[]
+ */
+ public $results = array();
+
+ /**
+ * An array of additional required parameters.
+ *
+ * @var array
+ */
+ public $params = array();
+
+ /**
+ * Constructs a DatastreamValidator.
+ *
+ * @param IslandoraFedoraObject $object
+ * The object to grab the datastream from.
+ * @param string $datastream
+ * The DSID of the datastream itself.
+ * @param array $params
+ * An extra array of parameters the validator might need.
+ */
+ public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) {
+ $this->object = $object;
+ $this->datastream = $datastream;
+ $this->params = $params;
+ if ($object[$datastream]) {
+ $this->datastreamContent = $object[$datastream]->content;
+ }
+ else {
+ drupal_set_message(t("Error grabbing content from datastream %datastream in object %id", array(
+ '%datastream' => $datastream,
+ '%id' => $object->id,
+ )), 'error');
+ }
+ }
+
+ /**
+ * Helper function to run all the validators in a class.
+ *
+ * On DatastreamValidator::__construct(), this looks for any functions
+ * within the class beginning in "assert" and runs them. In all current cases
+ * (and realistically in all future cases), this adds one or more passes or
+ * fails to $this->passes and/or $this->fails.
+ */
+ public function runValidators() {
+ $methods = get_class_methods($this);
+ foreach ($methods as $method) {
+ if (substr($method, 0, 6) === 'assert') {
+ $this->$method();
+ }
+ }
+ }
+
+ /**
+ * Returns an array of DatastreamValidatorResults.
+ *
+ * @return DatastreamValidatorResult[]
+ * The results.
+ */
+ public function getResults() {
+ if (empty($this->results)) {
+ $this->runValidators();
+ }
+ return $this->results;
+ }
+
+ /**
+ * Adds a result to $this->results.
+ *
+ * @param bool $type
+ * The type of result (TRUE for pass, FALSE for fail).
+ * @param string $message
+ * The message to put in the result.
+ */
+ public function addResult($type, $message) {
+ $result = new DatastreamValidatorResult($type, $message, $this->getAssertionCall());
+ $this->results[] = $result;
+ }
+
+ /**
+ * Cycles through backtrace until the first non-assertion method is found.
+ *
+ * This is a manipulated version of DrupalWebTestCase::getAssertionCall().
+ * We use it here so that we can pass back assertion calls from
+ * DatastreamValidator assertions instead of less useful TestCase functions.
+ *
+ * @return array
+ * Array representing the true caller.
+ */
+ protected function getAssertionCall() {
+ $backtrace = debug_backtrace();
+
+ // While the current caller's function starts with 'assert', and another one
+ // exists after this function, keep poppin' em off.
+ while (substr($backtrace[1]['function'], 0, 6) !== 'assert' && isset($backtrace[2])) {
+ array_shift($backtrace);
+ }
+
+ return _drupal_get_last_caller($backtrace);
+ }
+
+}
+
/**
* 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 string $datastream
- * A DSID to check that corresponds to a PHPGD-valid image datastream.
- *
- * @return array
- * A series of TRUE(pass)/FALSE(fail) results paired with result messages.
*/
-function islandora_validate_image_datastream($object, $datastream) {
- $datastream_string = $object[$datastream]->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;
+class ImageDatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Asserts the validity of an image using PHPGD.
+ */
+ protected function assertImageGeneration() {
+ $assertion = imagecreatefromstring($this->datastreamContent) !== FALSE;
+ $pass = "Image datastream {$this->datastream} is valid.";
+ $fail = "Image datastream {$this->datastream} is either invalid or corrupt.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
}
/**
* 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;
+class TIFFDatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Asserts that the TIFF contains an appropriate header.
+ */
+ public function assertTIFFHeaderHex() {
+ $datastream_header_hex = self::getTIFFHeaderHex();
+ if ($datastream_header_hex == "49492a00") {
+ // In this case, the ingested TIFF is designated as using the "Intel
+ // byte-order" (i.e. 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.'
+ $this->addResult(TRUE, "{$this->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" (i.e. big-endian) by starting with the characters "MM"
+ // instead. 42 follows once again, this time in big-endian hex.
+ $this->addResult(TRUE, "{$this->datastream} datastream asserts that it is a valid Motorola-byte-ordered TIF/TIFF file.");
+ }
+ else {
+ $this->addResult(FALSE, "{$this->datastream} datastream does not assert that it is a valid TIF/TIFF file.");
+ }
+ }
+
+ /**
+ * Grabs the first 8 characters from the TIFF datastream's hex.
+ *
+ * @return string
+ * The ... thing I just wrote up there.
+ */
+ protected function getTIFFHeaderHex() {
+ return substr(bin2hex($this->datastreamContent), 0, 8);
+ }
+
}
/**
- * 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.
+ * Asserts the validity of a JP2 datastream.
*/
-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;
+class JP2DatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Asserts the hex values at the head of the JP2 file.
+ *
+ * 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.
+ */
+ protected function assertJP2Header() {
+ $assertion = substr(bin2hex($this->datastreamContent), 8, 8) == '6a502020';
+ $pass = "Datastream {$this->datastream} contains the appropriate JP2 header.";
+ $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 header.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Asserts the marker at the end of the JP2 file.
+ *
+ * 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.
+ */
+ protected function assertJP2Marker() {
+ $assertion = substr(bin2hex($this->datastreamContent), strlen(bin2hex($this->datastreamContent)) - 4, 4) == 'ffd9';
+ $pass = "Datastream {$this->datastream} contains the appropriate JP2 ending marker.";
+ $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 ending marker. If this is the only JP2 validator that failed, it is likely that derivative generation was interrupted.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
}
/**
- * 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.
+ * Asserts the validity of a PDF datastream.
*/
-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;
+class PDFDatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Validates the PDF signature.
+ */
+ protected function assertPDFSignature() {
+ $assertion = substr($this->datastreamContent, 0, 5) == '%PDF-';
+ $pdf_version = substr($this->datastreamContent, 5, 3);
+ $pass = "{$this->datastream} datastream asserts that it is a valid PDF file using PDF version {$pdf_version}";
+ $fail = "{$this->datastream} datastream binary header appears to be corrupt and missing a valid PDF signature.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Counts the number of signatures in this PDF file and asserts there are any.
+ */
+ protected function assertPDFStreamCount() {
+ $pdf_stream_count = substr_count(bin2hex($this->datastreamContent), '0a73747265616d0a');
+ $assertion = $pdf_stream_count !== 0;
+ $pass = "{$this->datastream} datastream reports the existence of {$pdf_stream_count} PDF streams. Note that an extremely low number could still indicate corruption.";
+ $fail = "{$this->datastream} datastream contains zero PDF streams, and is likely not a PDF file.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Validates the PDF closing tag.
+ *
+ * @return bool
+ * TRUE if it was present; FALSE otherwise.
+ */
+ protected function assertPDFClosingTag() {
+ $assertion = strpos(bin2hex($this->datastreamContent), '0a2525454f460a') == TRUE;
+ $pass = "{$this->datastream} datastream reports the existence of the closing 'EOF' tag required at the end of PDFs";
+ $fail = "{$this->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.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
}
/**
- * Asserts that a string of text shows up inside a datastream.
+ * Validates the number of times a string occurs in 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.
+ * Requires $this->params to be set to an array containing two keys - the first
+ * is the string we're looking to find in the datastream, and the second is an
+ * integer representing the number of times it should appear in the datastream.
*/
-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;
+class TextDatastreamValidator extends DatastreamValidator {
+ /**
+ * Constructor override; blow up if we don't have our two values.
+ */
+ public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) {
+ if (count($params) < 2) {
+ throw new InvalidArgumentException('$params must contain at least two values to instantiate a TextDatastreamValidator.');
+ }
+ parent::__construct($object, $datastream, $params);
+ }
+
+ /**
+ * Asserts that the string given appears the correct number of times.
+ */
+ protected function assertTextStringCount() {
+ $string_count = self::getTextStringCount();
+ list($string, $expected) = $this->params;
+ $assertion = $string_count === $expected;
+ $this->addResult($assertion, "{$this->datastream} datastream contains the word(s) '{$string}' repeated {$string_count} time(s) (expected: {$expected}).");
+ }
+
+ /**
+ * The number of times key [0] in $this->params appears in the datastream.
+ *
+ * @return int
+ * That count I just mentioned up there.
+ */
+ protected function getTextStringCount() {
+ return substr_count($this->datastreamContent, $this->params[0]);
+ }
}
/**
- * Asserts the validity of any .wav datastraeam.
+ * Asserts the validity a WAV datastream.
*
* 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
@@ -242,54 +457,131 @@ function islandora_validate_text_datastream($object, $datastream, array $text) {
* 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;
+class WAVDatastreamValidator extends DatastreamValidator {
+
+ /**
+ * We need a special constructor here to get the hex datastream content.
+ *
+ * @param IslandoraFedoraObject $object
+ * The object to grab the datastream from.
+ * @param string $datastream
+ * The DSID of the datastream itself.
+ * @param array $params
+ * An extra array of parameters the validator might need.
+ */
+ public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) {
+ parent::__construct($object, $datastream, $params);
+ $this->datastreamContent = bin2hex($this->datastreamContent);
+ }
+
+ /**
+ * Asserts that the datastream contains a valid WAV signature.
+ */
+ protected function assertWAVSignature() {
+ $signatures = str_split(substr($this->datastreamContent, 0, 24), 8);
+ $assertion = $signatures[0] == '52494646' && $signatures[2] == '57415645';
+ $pass = "Header of the {$this->datastream} datastream contains a valid file signature.";
+ $fail = "Header of the {$this->datastream} datastream contains corrupt file signature.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Asserts that the chunksize in the header is correct.
+ */
+ protected function assertWAVChunkSize() {
+ $assertion = islandora_hex2int(substr($this->datastreamContent, 8, 8)) === 36 + self::getDataSubChunkSize();
+ $pass = "{$this->datastream} datastream chunksize in WAV header is correct";
+ $fail = "{$this->datastream} datastream chunksize in WAV header does not match actual chunksize.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Asserts that the datastream contains a 'fmt' subchunk.
+ */
+ protected function assertWAVFmtSubChunk() {
+ $assertion = substr($this->datastreamContent, 24, 8) === '666d7420';
+ $pass = "{$this->datastream} datastream contains a 'fmt' subchunk.";
+ $fail = "{$this->datastream} datastream is missing the required 'fmt' subchunk.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Asserts that the byterate reported by the WAV header is valid.
+ */
+ protected function assertWAVByteRate() {
+ $wav_samplerate = islandora_hex2int(substr($this->datastreamContent, 48, 8));
+ $assertion = islandora_hex2int(substr($this->datastreamContent, 56, 8)) === $wav_samplerate * self::getNumChannels() * self::getBytesPerSample();
+ $pass = "{$this->datastream} datastream byterate in the WAV header is correct.";
+ $fail = "{$this->datastream} datastream byterate in the WAV header does not match actual calculated byterate.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Asserts that the block alignment is correct.
+ */
+ protected function assertWAVBlockAlignment() {
+ $assertion = islandora_hex2int(substr($this->datastreamContent, 64, 4)) === self::getNumChannels() * self::getBytesPerSample();
+ $pass = "{$this->datastream} datastream block alignment is set correctly.";
+ $fail = "{$this->datastream} datastream block alignment is off.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+
+ /**
+ * Asserts the existence of a 'data' subchunk.
+ *
+ * Also asserts that the subchunk size is correct.
+ */
+ protected function assertWAVDataSubChunk() {
+ if (substr($this->datastreamContent, 72, 8) !== '64617461') {
+ $this->addResult(FALSE, "{$this->datastream} datastream is missing the 'data' subchunk.");
+ return;
+ }
+ else {
+ $this->addResult(TRUE, "{$this->datastream} datastream contains 'data' subchunk.");
+ $wav_numsamples = strlen(substr($this->datastreamContent, 88)) / self::getNumChannels() / self::getBytesPerSample() / 2;
+ $assertion = self::getDataSubChunkSize() === $wav_numsamples * self::getNumChannels() * self::getBytesPerSample();
+ $pass = "{$this->datastream} datastream 'data' chunk is the correct size.";
+ $fail = "{$this->datastream} datastream 'data' chunk is sized incorrectly.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
+ }
+
+ /**
+ * Gets the number of channels reported by the WAV header.
+ *
+ * @return int
+ * The number of channels reported by the datastream header.
+ */
+ protected function getNumChannels() {
+ return islandora_hex2int(substr($this->datastreamContent, 44, 4));
+ }
+
+ /**
+ * Gets the reported number of byte rates per sample.
+ *
+ * @return int
+ * The number of bytes per sample reported by the datastream header.
+ */
+ protected function getBytesPerSample() {
+ return islandora_hex2int(substr($this->datastreamContent, 68, 4)) / 8;
+ }
+
+ /**
+ * Gets the size of the 'data' subchunk.
+ *
+ * @return int
+ * The size of the 'data' subchunk.
+ */
+ protected function getDataSubChunkSize() {
+ return islandora_hex2int(substr($this->datastreamContent, 80, 8));
+ }
}
/**
@@ -300,76 +592,87 @@ function islandora_validate_wav_datastream($object, $datastream) {
* '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;
+class MP3DatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Asserts the validity of the MP3.
+ *
+ * The MP3 file format is a bit of a mess; the entire makeup of the file
+ * depends on whether it uses variable bit rate or static bit rate. So, I'm
+ * breaking my own rules here and using a single assert function so that I
+ * can handle the weird logic.
+ */
+ protected function assertValidMP3() {
+ $this->datastreamContent = bin2hex($this->datastreamContent);
+
+ // If it's not a VBR MP3, we don't have to check much, so let's get that
+ // out of the way first before we go doing a bunch of potentially pointless
+ // math. Check to see if the VBR flag (58696e67) isn't there.
+ if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffa') {
+ $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid MPEG-1 Layer 3 file with CRC protection");
+ return;
}
-
- // 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;
+ if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffb') {
+ $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid unprotected MPEG-1 Layer 3 file");
+ return;
}
- // We can't use the third flag for anything either.
- if (($mp3_flag_value + 8) % 8 > 3) {
- $mp3_field_offset[2] += 200;
+ // And what if the flag IS set?
+ if (strpos($this->datastreamContent, '58696e67')) {
+ // 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_vbrheader = substr($this->datastreamContent, strpos($this->datastreamContent, '58696e67'), 240);
+ $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));
+ $mp3_size = strlen($this->datastreamContent) / 2;
+ $assertion = $mp3_size == $mp3_field_bytes;
+ $pass = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes matches size field value of {$mp3_field_bytes}";
+ $fail = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes does not match size field value of {$mp3_field_bytes}";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ $mp3_field_offset[1] += 8;
+ $mp3_field_offset[2] += 8;
+ }
+
+ // We can't use the third flag for anything, but we still have to offset.
+ 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));
+ $assertion = $mp3_field_quality <= 100 && $mp3_field_quality >= 0;
+ $pass = "{$this->datastream} datastream reports valid VBR quality of {$mp3_field_quality} (expected: between 0-100)";
+ $fail = "{$this->datastream} datastream reports invalid VBR quality of {$mp3_field_quality} (expected: between 0-100)";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
}
- // 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);
+ // If none of that works out, fail.
+ else {
+ $this->addResult(FALSE, "{$this->datastream} datastream is corrupt and does not identify as a valid MP3.");
}
}
- // 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;
}
/**
@@ -378,27 +681,22 @@ function islandora_validate_mp3_datastream($object, $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
+ * a four-character filetype code. Here, 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;
+class MP4DatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Asserts that the datastream is ISO-formatted video.
+ */
+ protected function assertISOVideo() {
+ $mp4_ftyp = substr(strpos($this->datastreamContent, 'ftyp'), 4, 4);
+ $assertion = strpos($this->datastreamContent, 'ftyp') !== 0;
+ $pass = "{$this->datastream} datastream asserts that it is a valid ISO-formatted video file using ftyp {$mp4_ftyp}";
+ $fail = "{$this->datastream} datastream is not a valid ISO-formatted video";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
}
/**
@@ -409,33 +707,42 @@ function islandora_validate_mp4_datastream($object, $datastream) {
* 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);
+class OGGDatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Asserts that the datastream contains ogg pages.
+ */
+ protected function assertOGGPages() {
+ $ogg_pages = substr_count($this->datastreamContent, 'OggS');
+ $assertion = $ogg_pages !== 0;
+ $pass = "{$this->datastream} datastream asserts that it contains {$ogg_pages} Ogg pages (even a very small file should contain several).";
+ $fail = "{$this->datastream} datastream contains no Ogg pages.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
- $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);
+ /**
+ * Asserts that the datastream contains Theora-encoded video.
+ */
+ protected function assertTheoraVideo() {
+ $assertion = substr_count($this->datastreamContent, 'theora') !== 0;
+ $pass = "{$this->datastream} datastream asserts that it contains Theora-encoded video data.";
+ $fail = "{$this->datastream} datastream contains no marker indicating the presence of Theora-encoded video data.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
- return $results;
+ /**
+ * Asserts that the datastream contains Vorbis-encoded audio.
+ */
+ protected function assertVorbisAudio() {
+ $assertion = substr_count($this->datastreamContent, 'vorbis') !== 0;
+ $pass = "{$this->datastream} datastream asserts that it contains Vorbis-encoded audio data";
+ $fail = "{$this->datastream} datastream contains no marker indicating the presence of Vorbis-encoded audio data.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
}
/**
@@ -446,28 +753,29 @@ function islandora_validate_ogg_datastream($object, $datastream) {
* 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.
+ * after.
*/
-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);
+class MKVDatastreamValidator extends DatastreamValidator {
+
+ /**
+ * Asserts that the datastream is an EBML-format file.
+ */
+ protected function assertEBMLFormat() {
+ $assertion = substr(bin2hex($this->datastreamContent), 0, 8) == '1a45dfa3';
+ $pass = "{$this->datastream} datastream asserts that it is an EBML-formatted file";
+ $fail = "{$this->datastream} datastream is not an EBML-formatted file.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
- return $results;
+ /**
+ * Asserts that the datastream contains a matroska marker.
+ */
+ protected function assertMatroskaMarker() {
+ $assertion = substr_count($this->datastreamContent, 'matroska') == 1;
+ $pass = "{$this->datastream} datastream asserts that its EBML DocType is Matroska";
+ $fail = "{$this->datastream} datastream does not contain a Matroska EBML DocType marker.";
+ $message = $assertion ? $pass : $fail;
+ $this->addResult($assertion, $message);
+ }
}
diff --git a/tests/islandora_web_test_case.inc b/tests/islandora_web_test_case.inc
index 89ca9cbb..8a1e5ce8 100644
--- a/tests/islandora_web_test_case.inc
+++ b/tests/islandora_web_test_case.inc
@@ -216,37 +216,67 @@ class IslandoraWebTestCase extends DrupalWebTestCase {
/**
* 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.
+ * Datastream validation classes exist in, and can be added to, the file
+ * 'datastream_validators.inc', which is found in this folder. Datastream
+ * validator classes use the naming convention 'PrefixDatastreamValidator',
+ * and that 'Prefix' is what this function uses to determine what class to
+ * instantiate.
*
- * $param AbstractObject $object
+ * $param IslandoraFedoraObject $object
* The object to load datastreams from.
* $param array $datastreams
- * An array of paired DSIDs, validate function names, and optional params.
+ * An array of arrays that pair DSIDs, DatastreamValidator class prefixes,
+ * and optional params. You can check some of the existing implementations
+ * for examples.
*/
public function validateDatastreams($object, array $datastreams) {
+
if (!is_object($object)) {
- $this->fail("Failed. Object passed in is invalid.", 'Islandora');
+ $this->fail("Datastream validation failed; Object passed in is invalid.", 'Islandora');
+ return;
}
+
module_load_include('inc', 'islandora', 'tests/datastream_validators');
+
foreach ($datastreams as $datastream) {
- 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');
- }
+ // Let's give them conventional names.
+ $dsid = $datastream[0];
+ $prefix = $datastream[1];
+ $params = array();
+ if (isset($datastream[2])) {
+ $params = $datastream[2];
+ }
+
+ // Legacy tests were created before the CamelCase conventions of the class
+ // system now in place. So, we need to automagically seek out prefixes
+ // that start with a lower-case letter and convert them to the proper
+ // format (rather than fixing every single legacy test).
+ if (ctype_lower(substr($prefix, 0, 1))) {
+ // Handle the case where the prefix is "image".
+ if ($prefix === 'image') {
+ $prefix = 'Image';
+ }
+ // Handle the case where the prefix is "text".
+ elseif ($prefix === 'text') {
+ $prefix = 'Text';
}
+ // All other cases involve just converting everything to caps.
else {
- $this->fail("No {$datastream[0]} validation function exists for the {$datastream[1]} datastream.", 'Islandora');
+ $prefix = strtoupper($prefix);
+ }
+ }
+
+ // Instantiate the appropriate class, and grab the results.
+ $class_name = "{$prefix}DatastreamValidator";
+ if (class_exists($class_name)) {
+ $validator = new $class_name($object, $dsid, $params);
+ foreach ($validator->getResults() as $result) {
+ $this->assert($result->getType(), $result->getMessage(), 'Islandora', $result->getCaller());
}
}
+ else {
+ $this->fail("No DatastreamValidator class was found with the name '$class_name'; are you sure the prefix given to IslandoraWebTestCase->validateDatastreams() was entered correctly, or that such a validator exists?", 'Islandora');
+ }
}
}
@@ -315,7 +345,8 @@ class IslandoraWebTestCase extends DrupalWebTestCase {
* 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.
+ * 'models' - An array that can contain multiple content model PIDs, or a
+ * string containing a single content model PID.
* 'owner' - The object's owner. Defaults to the currently logged-in user,
* if available. It is recommended to set this to a value that can be found
* in $this->users; otherwise, this object will have to be manually deleted.
@@ -331,8 +362,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase {
* 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();
+ $tuque = islandora_get_tuque_connection($this->admin);
$repository = $tuque->repository;
if (!isset($properties['pid'])) {
$properties['pid'] = "islandora";
@@ -355,13 +385,14 @@ class IslandoraWebTestCase extends DrupalWebTestCase {
$object->owner = $this->loggedInUser->name;
}
- if (isset($properties['models']) && is_array($properties['models'])) {
- foreach ($properties['models'] as $model) {
- $object->relationships->add(FEDORA_MODEL_URI, 'hasModel', $model);
+ if (isset($properties['models'])) {
+ try {
+ $object->models = (array) $properties['models'];
+ }
+ catch (Exception $e) {
+ $this->fail("Encountered an exception when trying to add content models to {$object->id}: $e");
+ return FALSE;
}
- }
- 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');
}
$repository->ingestObject($object);
@@ -442,4 +473,132 @@ QUERY;
}
}
+ /**
+ * These are a few quick helper functions to fill in a gap in the standard
+ * DrupalWebTestCase where no function exists to test for the simple existence
+ * or non-existence of an error.
+ */
+
+ /**
+ * Asserts that an error is found in $this->content.
+ *
+ * @param string $message
+ * The message to pass on to the results.
+ * @param string $group
+ * The group to place the result in.
+ *
+ * @return bool
+ * TRUE on success, FALSE on failure.
+ */
+ public function assertError($message = '', $group = 'Other') {
+ if (!$message) {
+ $message = "Error found on current page when error was expected.";
+ }
+ return $this->assertRaw("