= 0) && ($bytes < $kilobyte)) { return $bytes . ' B'; } elseif (($bytes >= $kilobyte) && ($bytes < $megabyte)) { return round($bytes / $kilobyte, $precision) . ' KiB'; } elseif (($bytes >= $megabyte) && ($bytes < $gigabyte)) { return round($bytes / $megabyte, $precision) . ' MiB'; } elseif (($bytes >= $gigabyte) && ($bytes < $terabyte)) { return round($bytes / $gigabyte, $precision) . ' GiB'; } elseif ($bytes >= $terabyte) { return round($bytes / $terabyte, $precision) . ' TiB'; } else { return $bytes . ' B'; } } /** * Add a file as managed if is not already. * * @param string $file_uri * The given file URI location. * * @return object * The file, as returned from file_save(). */ function islandora_temp_file_entry($file_uri, $mime = NULL) { $query = new EntityFieldQuery(); $result = $query ->entityCondition('entity_type', 'file') ->propertyCondition('uri', $file_uri) ->execute(); if (isset($result['file'])) { $fid = current($result['file'])->fid; $file = file_load($fid); } else { $file = new stdClass(); $file->uri = $file_uri; $file->filename = drupal_basename($file_uri); if (isset($mime)) { $file->filemime = $mime; } else { $file->filemime = file_get_mimetype($file_uri); } } $file->status = 0; return file_save($file); } /** * Creates a label for control group symbols. */ function islandora_control_group_to_human_readable($control_group) { switch ($control_group) { case 'M': return 'Managed'; case 'X': return 'Inline XML'; case 'R': return 'Redirect'; case 'E': return 'Externally Referenced'; default: return $control_group; } } /** * Checks if the given pid is valid. * * @param string $pid * The object id to check. * * @return bool * TRUE if valid, FALSE otherwise. */ function islandora_is_valid_pid($pid) { return drupal_strlen(trim($pid)) <= 64 && preg_match('/^([A-Za-z0-9]|-|\.)+:(([A-Za-z0-9])|-|\.|~|_|(%[0-9A-F]{2}))+$/', trim($pid)); } /** * Checks if the given namespace is valid. * * @param string $namespace * The namespace to check without the ":" character. * * @return bool * TRUE if valid, FALSE otherwise. */ function islandora_is_valid_namespace($namespace) { return drupal_strlen(trim($namespace)) <= 64 && preg_match('/^([A-Za-z0-9]|-|\.)+$/', trim($namespace)); } /** * Checks if the given datastream id is valid. * * @param string $dsid * The datastream id to check. * * @return bool * TRUE if valid, FALSE otherwise. */ function islandora_is_valid_dsid($dsid) { return drupal_strlen(trim($dsid)) <= 64 && preg_match('/^[a-zA-Z0-9\_\-\.]+$/', trim($dsid)); } /** * Helper function to describe a Fedora repository. * * Can be used to check if Fedora is available. * * @param string $url * A url to a Fedora repository, if NULL the default is used. * * @return array * Returns an array describing the repository. Returns FALSE if Fedora is down * or if the url is incorrect. */ function islandora_describe_repository($url = NULL) { $url = isset($url) ? $url : variable_get('islandora_base_url', 'http://localhost:8080/fedora'); $connection = islandora_get_tuque_connection(); if ($connection) { try { $info = $connection->api->a->describeRepository(); return $info; } catch (RepositoryException $e) { return FALSE; } } return FALSE; } /** * Build and invoke a list of hooks by combining the given hook and refinements. * * The given hook will be called as MODULE_HOOK, and for each hook refinement * as MODULE_REFINEMENT_HOOK. Any additional arguments passed to this function * will be passed as arguments to module_invoke_all(). * * @see islandora_build_hook_list() * * @param string $hook * A hook to call. * @param array $refinements * An array of strings, that will be escaped and concatinated with the given * hook. This will most likely be PIDs/DSIDs/Labels etc. We often refine our * hooks using an objects model. * @param array $args * Any arguments to pass onto module_invoke_all(). * * @return array * The merged results from all the hooks. */ function islandora_invoke_hook_list($hook, array $refinements, array $args) { $return = array(); foreach (islandora_build_hook_list($hook, $refinements) as $refined_hook) { array_unshift($args, $refined_hook); $result = call_user_func_array('module_invoke_all', $args); $return = array_merge_recursive($return, $result); array_shift($args); } if (module_exists('rules')) { $event_info = rules_get_event_info($hook); if (isset($event_info['module'])) { $parameters = $event_info['variables']; $rule_args = array_slice($args, 0, count($parameters)); array_unshift($rule_args, $hook); $result = call_user_func_array('rules_invoke_event', $rule_args); } } return $return; } /** * Build a list of all the hooks to call. * * Concatenates each hook $refinement (escaped) to the hook name, for calling * with module_invoke_all(). * * Any non-valid PHP function characters in the given refinements are * converted to "_" characters. * * @param string $hook * The base hook to concatenate. * @param array $refinements * An array of strings, that will be escaped and concatinated with the given * hook. This will most likely be PIDs/DSIDs/Labels etc. We often refine our * hooks using an objects model. * * @return array * An array with each refinement escaped and concatenated with the base hook * name, in addition to the base hook name. */ function islandora_build_hook_list($hook, $refinements = array()) { $refinements = array_unique($refinements); $hooks = array($hook); foreach ($refinements as $refinement) { $refinement = preg_replace('/[^a-zA-Z0-9_]/', '_', $refinement); $hooks[] = "{$refinement}_{$hook}"; } return $hooks; } /** * Escape a Fedora PID to be valid inside of a PHP function name. * * Originally intended to allow inclusion of a PID in a module_invoke_all() * call. */ function islandora_escape_pid_for_function($pid) { // Apparently, case doesn't matter for function calls in PHP, so let's not // really worry about changing the case. return str_replace( // Any PID characters which are not valid in the name of a PHP function. array( ':', '-', ), '_', $pid ); } /** * Gets the namespace of the given id. * * @param string $id * Either a PID or namespace to check for accessibility. Any string like those * below are fine. * * @code * 'islandora', * 'islandora:', * 'islandora:1234', * @endcode * * @return string * The namespace portion of the given string. */ function islandora_get_namespace($id) { $matches = array(); preg_match('/^([^:]*)/', $id, $matches); return $matches[0]; } /** * Checks the given namespace/PID is/has an accessible namespace. * * Accessible is defined by the "islandora_pids_allowed" variable. * * @param string $id * Either a PID or namespace to check for accessibility. Any string like those * below are fine. * * @code * 'islandora', * 'islandora:', * 'islandora:1234', * @endcode * * @return bool * TRUE if accessible, FALSE otherwise. */ function islandora_namespace_accessible($id) { if (variable_get('islandora_namespace_restriction_enforced', FALSE)) { $namespace = islandora_get_namespace($id); $allowed_namespaces = islandora_get_allowed_namespaces(); return in_array($namespace, $allowed_namespaces); } return TRUE; } /** * Gets any objects that the given object has a parent relationship with. * * Parent relationships are defined as (isMemberOf, isMemberOfCollection). * * This function gets its info from the RELS-EXT directly rather than through an * risearch. * * @param AbstractObject $object * The object whose parents will be returned. * * @return array * An array of FedoraObject's that the given object has a * (isMemberOf, isMemberOfCollection) relationship with. */ function islandora_get_parents_from_rels_ext(AbstractObject $object) { try { $collections = array_merge( $object->relationships->get(FEDORA_RELS_EXT_URI, 'isMemberOfCollection'), $object->relationships->get(FEDORA_RELS_EXT_URI, 'isMemberOf')); } catch (RepositoryException $e) { // @todo some logging would be nice, not sure what this throws. return array(); } $map = function($o) { return islandora_object_load($o['object']['value']); }; $collections = array_map($map, $collections); return array_filter($collections); } /** * Gets the datastreams requirments that are missing. * * @param AbstractObject $object * The object which models will be used to determine what datastreams it * should have. * * @return array * The DS-COMPOSITE-MODEL defined datastreams that are required for the given * object, but not already present. */ function islandora_get_missing_datastreams_requirements(AbstractObject $object) { module_load_include('inc', 'islandora', 'includes/utilities'); $datastreams = islandora_get_datastreams_requirements($object); foreach ($datastreams as $dsid => $requirements) { if (isset($object[$dsid])) { unset($datastreams[$dsid]); } } return $datastreams; } /** * Gets the required datastreams for the given object. * * Checks the object's content model's for which datastream are expected to be * used with this object, as defined by the DS-COMPOSITE-MODEL datastreams. * * For duplicate datastreams in the models, the first model defines the * datastreams attributes regardless of what other models define. * This should be undefined behavior according to the documentation. * * @link https://wiki.duraspace.org/display/FEDORA34/Fedora+Digital+Object+Model#FedoraDigitalObjectModel-ContentModelObjectCMODEL @endlink * * @see islandora_get_required_datastreams_from_content_model() * * @param AbstractObject $object * The object which models will be used to determine what datastreams it * should have. * * @return array * The DS-COMPOSITE-MODEL defined datastreams that are required for the given * object. */ function islandora_get_datastreams_requirements(AbstractObject $object) { return islandora_get_datastreams_requirements_from_models($object->models); } /** * Get the list of which datastreams are valid in the given set of models. * * @param array $models * An array of content models PIDs from which to parse the DS-COMPOSITE-MODEL * stream. * * @return array * An associative array of associative arrays, merged from calls to * islandora_get_datastreams_requirements_from_content_model(). */ function islandora_get_datastreams_requirements_from_models(array $models) { $dsids = array(); foreach ($models as $model_pid) { $model = islandora_object_load($model_pid); if (isset($model) && $model) { $dsids += islandora_get_datastreams_requirements_from_content_model($model); } } // The AUDIT Datastream can not really be added, so it can't really be // missing. unset($dsids['AUDIT']); return $dsids; } /** * Checks the given content model for which datastreams are required. * * As defined by it's DS-COMPOSITE-MODEL datastream. * * @todo Add support for fetching the schema information. * * @param AbstractObject $object * The content model whose DS-COMPOSITE-MODEL datastream will be used to * determine what datastreams are required. * * @return array * An associative array mapping datastream IDs to associative arrays * containing the values parsed from the DS-COMPOSITE-MODEL on the given * object--of the form: * - DSID: A datastream ID being described. * - "id": A string containing ID of the datastream. * - "mime": A array containing MIME-types the stream may have. * - "optional": A boolean indicating if the given stream is optional. */ function islandora_get_datastreams_requirements_from_content_model(AbstractObject $object) { if (empty($object[ISLANDORA_DS_COMP_STREAM]) || !islandora_datastream_access(ISLANDORA_VIEW_OBJECTS, $object[ISLANDORA_DS_COMP_STREAM])) { return array(); } $xml = new SimpleXMLElement($object[ISLANDORA_DS_COMP_STREAM]->content); foreach ($xml->dsTypeModel as $ds) { $dsid = (string) $ds['ID']; $optional = strtolower((string) $ds['optional']); $mime = array(); foreach ($ds->form as $form) { $mime[] = (string) $form['MIME']; } $dsids[$dsid] = array( 'id' => $dsid, 'mime' => $mime, 'optional' => ($optional == 'true') ? TRUE : FALSE, ); } return $dsids; } /** * Prepare an ingestable object. * * @param string $name_source * Either a pid or namespace in which the PID for the new object will be * created. * @param string $label * An optional label to apply to the object. * @param array $datastreams * A array of datastreams to add, where each datastream definition is an * associative array containing: * - dsid: The datastream ID. * - label: An optional label for the datastream. * - mimetype: A MIMEtype for the datastream; defaults to text/xml. * - control_group: One of X, M, R and E; defaults to M. * - datastream_file: A web-accessible path, for which we try to get an * absolute path using url(). * @param array $content_models * An array of content model PIDs to which the new object should subscribe. * @param array $relationships * An array of relationships, where each relationship is an associative array * containing: * - relationship: The predicate for the relationship, from the Fedora * RELS-EXT namespace. * - pid: The object for the relationship, to which we are creating the * relationhsip. * * @return NewFedoraObject * An ingestable NewFedoraObject. */ function islandora_prepare_new_object($name_source = NULL, $label = NULL, $datastreams = array(), $content_models = array(), $relationships = array()) { global $user; $tuque = islandora_get_tuque_connection(); $object = isset($name_source) ? $tuque->repository->constructObject($name_source) : new IslandoraNewFedoraObject(NULL, $tuque->repository); $object->owner = isset($user->name) ? $user->name : $object->owner; $object->label = isset($label) ? $label : $object->label; foreach ($content_models as $content_model) { $object->relationships->add(FEDORA_MODEL_URI, 'hasModel', $content_model); } foreach ($relationships as $relationship) { $object->relationships->add(FEDORA_RELS_EXT_URI, $relationship['relationship'], $relationship['pid']); } foreach ($datastreams as $ds) { $dsid = $ds['dsid']; $label = isset($ds['label']) ? $ds['label'] : ''; $mimetype = isset($ds['mimetype']) ? $ds['mimetype'] : 'text/xml'; // Default 'Managed' $control_group = 'M'; $groups = array('X', 'M', 'R', 'E'); if (isset($ds['control_group']) && in_array($ds['control_group'], $groups)) { $control_group = $ds['control_group']; } $as_file = FALSE; if (file_valid_uri($ds['datastream_file'])) { // A local file with as a Drupal file/stream wrapper URI. $datastream_file = $ds['datastream_file']; $as_file = TRUE; } elseif (is_readable($ds['datastream_file'])) { // A local file as a filesystem path. $datastream_file = drupal_realpath($ds['datastream_file']); $as_file = TRUE; } else { $scheme = parse_url($ds['datastream_file'], PHP_URL_SCHEME); if (in_array($scheme, stream_get_wrappers())) { // A URI which gets handled by one of the PHP-native stream wrappers. $datastream_file = $ds['datastream_file']; $as_file = TRUE; } else { // Schema does not match available php stream wrapper. Attempt to // set datastream_file by url for the given scheme. Https (SSL) can // cause this to fail, and trigger an output log in watchdog. $datastream_file = url($ds['datastream_file'], array('absolute' => TRUE)); watchdog( 'islandora', 'Attempting to ingest %file in islandora_prepare_new_object(), but it does not appear to be readable. We will pass the path through url(), and pass along as such.', array('%file' => $datastream_file), WATCHDOG_WARNING ); } } $datastream = $object->constructDatastream($dsid, $control_group); $datastream->label = $label; $datastream->mimetype = $mimetype; switch ($control_group) { case 'M': if ($as_file) { $datastream->setContentFromFile($datastream_file); } else { $datastream->setContentFromUrl($datastream_file); } break; case 'X': $datastream->setContentFromString(file_get_contents($datastream_file)); break; } $object->ingestDatastream($datastream); } return $object; } /** * Displays the repository is inaccessible message. * * Use anywhere we want to ensure a consitent error message when the repository * is not accessible. */ function islandora_display_repository_inaccessible_message() { $text = t('Islandora configuration'); $link = l($text, 'admin/islandora/configure', array('attributes' => array('title' => $text))); $message = t('Could not connect to the repository. Please check the settings on the !link page.', array('!link' => $link)); drupal_set_message(filter_xss($message), 'error', FALSE); } /** * Create a message stating if the given executable is available. * * If the both the version and required version are given then only if the * version is equal to or greater than the required version the executable * will be considered correct. * * @param string $path * The absolute path to an executable to check for availability. * @param string $version * The version of exectuable. * @param string $required_version * The required version of exectuable. * * @return string * A message in html detailing if the given executable is accessible. */ function islandora_executable_available_message($path, $version = NULL, $required_version = NULL) { $available = is_executable($path); if ($available) { $image = theme_image(array('path' => 'misc/watchdog-ok.png', 'attributes' => array())); $message = t('Executable found at @path', array('@path' => $path)); if ($version) { $message .= t('
Version: @version', array('@version' => $version)); } if ($required_version) { $message .= t('
Required Version: @version', array('@version' => $required_version)); if (version_compare($version, $required_version) < 0) { $image = theme_image(array('path' => 'misc/watchdog-error.png', 'attributes' => array())); } } } else { $image = theme_image(array('path' => 'misc/watchdog-error.png', 'attributes' => array())); $message = t('Unable to locate executable at @path', array('@path' => $path)); } return $image . $message; } /** * Create a message stating if the given directory exists. * * @param string $path * The absolute path to an executable to check for availability. * * @return string * A message in HTML detailing if the given directory is exists. */ function islandora_directory_exists_message($path) { $available = is_dir($path); if ($available) { $image = theme_image(array('path' => 'misc/watchdog-ok.png', 'attributes' => array())); $message = t('Directory found at @path', array('@path' => $path)); } else { $image = theme_image(array('path' => 'misc/watchdog-error.png', 'attributes' => array())); $message = t('Unable to locate directory at @path', array('@path' => $path)); } return $image . $message; } /** * Gets the default value for the given system_settings_form() element. * * Checks the $form_state for the default value if not it checks * variable_get(). Assumes #tree is FALSE. This is only really required * for elements that utilize AJAX, as their value can change before the * submit actually takes place. * * @param string $name * The name of the system settings form element. * @param mixed $default_value * The default value for the system settings form element. * @param array $form_state * The Drupal form state. * * @return mixed * The default value for use in a the system_settings_form(). */ function islandora_system_settings_form_default_value($name, $default_value, array &$form_state) { return isset($form_state['values'][$name]) ? $form_state['values'][$name] : variable_get($name, $default_value); } /** * Gets the list of allowed namespaces as defined by 'islandora_pids_allowed'. * * @return array * The list of namespaces striped of trailing ":" characters. */ function islandora_get_allowed_namespaces() { $matches = array(); $allowed_namespaces = variable_get('islandora_pids_allowed', 'default: demo: changeme: islandora:'); preg_match_all('/([A-Za-z0-9-\.]+):/', $allowed_namespaces, $matches); $accessible_namespaces = $matches[1]; // Ensure that the "islandora" namespace is explicitly allowed // no matter what happens. if (!in_array('islandora', $accessible_namespaces)) { $accessible_namespaces[] = 'islandora'; } return $accessible_namespaces; } /** * Gets a list of all existing content models. * * If 'islandora_namespace_restriction_enforced' is set to true only return * content models in the allowed namespace. * * @param bool $ignore_system_namespace * Ignore content models in the 'fedora-system' namespace. * * @return array * An associative array of all existing content models. * - pid: The PID of the content model object. * - pid: The PID of the content model object. * - label: The label of the content model object. */ function islandora_get_content_models($ignore_system_namespace = TRUE) { module_load_include('inc', 'islandora', 'includes/utilities'); $tuque = islandora_get_tuque_connection(); $query = "PREFIX fm: <" . FEDORA_MODEL_URI . "> PREFIX fr: <" . FEDORA_RELS_EXT_URI . "> SELECT ?object ?label FROM <#ri> WHERE { {?object fm:hasModel ; fm:state fm:Active } UNION{ ?object fr:isMemberOfCollection ; fm:state fm:Active } OPTIONAL{ ?object fm:label ?label } }"; $content_models = array(); $results = $tuque->repository->ri->sparqlQuery($query, 'unlimited'); foreach ($results as $result) { $content_model = $result['object']['value']; $label = $result['label']['value']; $namespace = islandora_get_namespace($content_model); $ignore = $ignore_system_namespace && $namespace == 'fedora-system'; $ignore |= !islandora_namespace_accessible($namespace); if (!$ignore) { $content_models[$content_model] = array('pid' => $content_model, 'label' => $label); } } return $content_models; } /** * Returns Drupal tableselect element allowing selection of Content Models. * * @param string $drupal_variable * the name of the Drupal variable holding selected content models * Content models held in this variable will appear at the top of * the displayed list * @param array $default_values_array * default values to display if $drupal_variable is unset * * @return array * Drupal form element allowing content model selection */ function islandora_content_model_select_table_form_element($drupal_variable, $default_values_array = array('')) { $defaults = array(); $rows = array(); $content_models = array(); $options = islandora_get_content_models(TRUE); foreach ($options as $option) { $content_models[$option['pid']] = $option['label']; } $selected = array_values(variable_get($drupal_variable, $default_values_array)); $comparator = function ($a, $b) use ($selected) { $a_val = $b_val = 0; if (in_array($a, $selected)) { $a_val = 1; } if (in_array($b, $selected)) { $b_val = 1; } return $b_val - $a_val; }; uksort($content_models, $comparator); foreach ($content_models as $pid => $label) { $rows[$pid] = array( 'pid' => $pid, 'title' => $label, ); $defaults[$pid] = in_array($pid, $selected); } $header = array( 'pid' => array('data' => t('PID')), 'title' => array('data' => t('Content Model')), ); // Build and return table element. $element = array( '#type' => 'tableselect', '#header' => $header, '#options' => $rows, '#default_value' => $defaults, '#empty' => t("There are no content models in this Fedora Repository."), ); return $element; } /** * Convience function for generating a E_USER_DEPRECATED message. * * To utilitize this function pass the results to trigger_error() like so: * * @code * $message = islandora_deprecated('7.x-1.1', t('Use more cowbell.')) * trigger_error(filter_xss($message), E_USER_DEPRECATED) * @endcode * * @param string $release * The release the calling function was depreciated in. * @param string $solution * A message describing an alternative solution to the deprecated function. * It's assumed to be already passed though the t() function. * * @return string * The deprecated message. */ function islandora_deprecated($release, $solution = NULL) { $bt = debug_backtrace(); assert($bt[0]['function'] == __FUNCTION__); $function = $bt[1]['function']; $message = t('@function() has been deprecated. As of @release, please update your code before the next release.', array( '@function' => $function, '@release' => $release, )); if (isset($solution)) { $message .= "
\n" . $solution; } return $message; } /** * Transform recursively-merged array of strings to renderable arrays. * * Renderable arrays are passed-through as-is. * * Previously, functions/hooks like islandora_view_object would return an * associative array with string values containing markup. These values were * then imploded into one large piece of markup. Here, we transform this older * structure which was generated into a renderable array, because renderable * arrays are awesome! * * @param array $markup_array * An associative array of which the values are either a string or an array * of strings, which we transform to renderable markup elements (by * reference). */ function islandora_as_renderable_array(&$markup_array) { foreach ($markup_array as &$value) { if (!is_array($value)) { // Not a renderable array, just a string. Let's convert it to a // renderable '#markup' element. $value = array('#markup' => $value); } elseif (!isset($value['#type']) && !isset($value['#markup'])) { // A simple array--possibly the result of a recursive merge? Let's // look at each, to possibly convert them to a renderable '#markup' // elements. foreach ($value as &$inner) { if (!is_array($inner)) { // If it is an array at this level, we can assume that it is a // renderable array. If it is not an array, convert to a renderable // '#markup' element. $inner = array('#markup' => $inner); } } unset($inner); } } unset($value); } /** * Sanitizes an input string to be valid XML. * * @param string $input * An input string. * @param string $replacement * What we are replacing invalid characters with, defaults to ''. * * @return string * The sanitized string. */ function islandora_sanitize_input_for_valid_xml($input, $replacement = '') { $input = preg_replace('/[^\x9\xA\xD\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/u', $replacement, $input); return $input; } /** * Page callback for session status messages to be sent to drupal_set_message(). * * @return array * Render array containing markup. */ function islandora_event_status() { $results = FALSE; if (isset($_SESSION['islandora_event_messages'])) { foreach ($_SESSION['islandora_event_messages'] as $message) { drupal_set_message($message['message'], $message['severity']); $results = TRUE; } unset($_SESSION['islandora_event_messages']); } $text = ($results) ? t('The status messages above will be deleted after viewing this page.') : t('No messages to display.'); return array('#markup' => $text); } /** * Scales the given image. * * @param object $file * The image file to scale. * @param int $width * The width to scale the derived image to. * @param int $height * The height to scale the derived image to. * * @return bool * TRUE if successful, FALSE otherwise. */ function islandora_scale_thumbnail($file, $width, $height) { $real_path = drupal_realpath($file->uri); $image = image_load($real_path); try { if (!empty($image)) { $scale = image_scale($image, $width, $height, TRUE); if ($scale) { return image_save($image); } } } catch (exception $e) { drupal_set_message(t( "Islandora failed to scale image with message: '@message'", array("@message" => $e->getMessage()))); watchdog( 'islandora', 'Islandora failed to scale image.
With stack: @trace', array('@trace' => $e->getTraceAsString()), WATCHDOG_ERROR ); } return FALSE; } /** * Determines if the server operating system is Windows. * * @return bool * TRUE if Windows, FALSE otherwise. */ function islandora_deployed_on_windows() { // Determine if PHP is currently running on Windows. if (strpos(strtolower(php_uname('s')), 'windows') !== FALSE) { return TRUE; } return FALSE; } /** * Build the edit registry for a given datastream. * * @param AbstractDatastream $datastream * The datastream being edited. * * @return array * The built edit registry array. */ function islandora_build_datastream_edit_registry(AbstractDatastream $datastream) { $edit_registry = module_invoke_all(ISLANDORA_EDIT_DATASTREAM_REGISTRY_HOOK, $datastream->parent, $datastream); $context = array( 'object' => $datastream->parent, 'datastream' => $datastream, 'original_edit_registry' => $edit_registry, ); drupal_alter(ISLANDORA_EDIT_DATASTREAM_REGISTRY_HOOK, $edit_registry, $context); return $edit_registry; }