diff options
Diffstat (limited to 'modules/file/file.module')
-rw-r--r-- | modules/file/file.module | 960 |
1 files changed, 960 insertions, 0 deletions
diff --git a/modules/file/file.module b/modules/file/file.module new file mode 100644 index 000000000..21b147889 --- /dev/null +++ b/modules/file/file.module @@ -0,0 +1,960 @@ +<?php +// $Id$ + +/** + * @file + * Defines a "managed_file" Form API field and a "file" field for Field module. + */ + +// Load all Field module hooks for File. +require(DRUPAL_ROOT . '/modules/file/file.field.inc'); + +/** + * Implement hook_menu(). + */ +function file_menu() { + $items = array(); + + $items['file/ajax'] = array( + 'page callback' => 'file_ajax_upload', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + $items['file/progress'] = array( + 'page callback' => 'file_ajax_progress', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Implement hook_elements(). + */ +function file_elements() { + $elements = array(); + + $file_path = drupal_get_path('module', 'file'); + + // The managed file element may be used independently anywhere in Drupal. + $elements['managed_file'] = array( + '#input' => TRUE, + '#process' => array('file_managed_file_process'), + '#value_callback' => 'file_managed_file_value', + '#element_validate' => array('file_managed_file_validate'), + '#theme' => 'file_managed_file', + '#theme_wrappers' => array('form_element'), + '#progress_indicator' => 'throbber', + '#progress_message' => NULL, + '#upload_validators' => array(), + '#upload_location' => NULL, + '#extended' => FALSE, + '#attached_css' => array($file_path . '/file.css'), + '#attached_js' => array($file_path . '/file.js'), + ); + + return $elements; +} + +/** + * Implement hook_theme(). + */ +function file_theme() { + return array( + // file.module. + 'file_link' => array( + 'arguments' => array('file' => NULL), + ), + 'file_icon' => array( + 'arguments' => array('file' => NULL), + ), + 'file_managed_file' => array( + 'arguments' => array('element' => NULL), + ), + + // file.field.inc. + 'file_widget' => array( + 'arguments' => array('element' => NULL), + ), + 'file_widget_multiple' => array( + 'arguments' => array('element' => NULL), + ), + 'file_upload_help' => array( + 'arguments' => array('upload_validators' => NULL), + ), + 'field_formatter_file_default' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_file_table' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_file_url_plain' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * Implement hook_file_download(). + */ +function file_file_download($uri) { + global $user; + + // Get the file record based on the URI. If not in the database just return. + $files = file_load_multiple(array(), array('uri' => $uri)); + if (count($files)) { + $file = reset($files); + } + else { + return; + } + + // Find out which (if any) file fields contain this file. + $references = file_get_file_references($file); + + // TODO: Check field-level access if available here. + + $denied = NULL; + // Check access to content containing the file fields. If access is allowed + // to any of this content, allow the download. + foreach ($references as $field_name => $field_references) { + foreach ($field_references as $obj_type => $type_references) { + foreach ($type_references as $reference) { + // If access is allowed to any object, immediately stop and grant + // access. If access is denied, continue through in case another object + // grants access. + // TODO: Switch this to a universal access check mechanism if available. + if ($obj_type == 'node' && ($node = node_load($reference->nid))) { + if (node_access('view', $node)) { + $denied = FALSE; + break 3; + } + else { + $denied = TRUE; + } + } + if ($obj_type == 'user') { + if (user_access('access user profiles') || $user->uid == $reference->uid) { + $denied = FALSE; + break 3; + } + else { + $denied = TRUE; + } + } + } + } + } + + // No access was denied or granted. + if (!isset($denied)) { + return; + } + // Access specifically denied and not granted elsewhere. + elseif ($denied == TRUE) { + return -1; + } + + // Access is granted. + $name = mime_header_encode($file->filename); + $type = mime_header_encode($file->filemime); + // Serve images, text, and flash content for display rather than download. + $inline_types = variable_get('file_inline_types', array('^text/', '^image/', 'flash$')); + $disposition = 'attachment'; + foreach ($inline_types as $inline_type) { + // Exclamation marks are used as delimiters to avoid escaping slashes. + if (preg_match('!' . $inline_type . '!', $file->filemime)) { + $disposition = 'inline'; + } + } + + return array( + 'Content-Type' => $type . '; name="' . $name . '"', + 'Content-Length' => $file->filesize, + 'Content-Disposition' => $disposition . '; filename="' . $name . '"', + 'Cache-Control' => 'private', + ); +} + +/** + * Menu callback; Shared AJAX callback for file uploads and deletions. + * + * This rebuilds the form element for a particular field item. As long as the + * form processing is properly encapsulated in the widget element the form + * should rebuild correctly using FAPI without the need for additional callbacks + * or processing. + */ +function file_ajax_upload() { + $form_parents = func_get_args(); + $form_build_id = (string) array_pop($form_parents); + + if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) { + // Invalid request. + drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error'); + $commands = array(); + $commands[] = ajax_command_replace(NULL, theme('status_messages')); + ajax_render($commands, FALSE); + } + + list($form, $form_state, $form_id, $form_build_id) = ajax_get_form(); + + if (!$form) { + // Invalid form_build_id. + drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error'); + $commands = array(); + $commands[] = ajax_command_replace(NULL, theme('status_messages')); + ajax_render($commands, FALSE); + } + + // Get the current element and count the number of files. + $current_element = $form; + foreach ($form_parents as $parent) { + $current_element = $current_element[$parent]; + } + $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0; + + // Build, validate and if possible, submit the form. + drupal_process_form($form_id, $form, $form_state); + + // This call recreates the form relying solely on the form_state that the + // drupal_process_form() set up. + $form = drupal_rebuild_form($form_id, $form_state, $form_build_id); + + // Retrieve the element to be rendered. + foreach ($form_parents as $parent) { + $form = $form[$parent]; + } + + // Add the special AJAX class if a new file was added. + if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) { + $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content'; + } + // Otherwise just add the new content class on a placeholder. + else { + $form['#suffix'] .= '<span class="ajax-new-content"></span>'; + } + + $output = theme('status_messages') . drupal_render($form); + $js = drupal_add_js(); + $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']); + + $commands = array(); + $commands[] = ajax_command_replace(NULL, $output, $settings); + ajax_render($commands, FALSE); +} + +/** + * Menu callback for upload progress. + * + * @param $key + * The unique key for this upload process. + */ +function file_ajax_progress($key) { + $progress = array( + 'message' => t('Starting upload...'), + 'percentage' => -1, + ); + + $implementation = file_progress_implementation(); + if ($implementation == 'uploadprogress') { + $status = uploadprogress_get_info($key); + if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) { + $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['bytes_uploaded']), '@total' => format_size($status['bytes_total']))); + $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']); + } + } + elseif ($implementation == 'apc') { + $status = apc_fetch('upload_' . $key); + if (isset($status['current']) && !empty($status['total'])) { + $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['current']), '@total' => format_size($status['total']))); + $progress['percentage'] = round(100 * $status['current'] / $status['total']); + } + } + + drupal_json($progress); +} + +/** + * Determine the preferred upload progress implementation. + * + * @return + * A string indicating which upload progress system is available. Either "apc" + * or "uploadprogress". If neither are available, returns FALSE. + */ +function file_progress_implementation() { + static $implementation; + if (!isset($implementation)) { + $implementation = FALSE; + + // We prefer the PECL extension uploadprogress because it supports multiple + // simultaneous uploads. APC only supports one at a time. + if (extension_loaded('uploadprogress')) { + $implementation = 'uploadprogress'; + } + elseif (extension_loaded('apc') && ini_get('apc.rfc1867')) { + $implementation = 'apc'; + } + } + return $implementation; +} + +/** + * Implement hook_file_references(). + */ +function file_file_references($file) { + $count = file_get_file_reference_count($file); + return $count ? array('file' => $count) : NULL; +} + +/** + * Implement hook_file_delete(). + */ +function file_file_delete($file) { + // TODO: Remove references to a file that is in-use. +} + +/** + * Process function to expand the managed_file element type. + * + * Expands the file type to include Upload and Remove buttons, as well as + * support for a default value. + */ +function file_managed_file_process($element, &$form_state, $form) { + $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0; + + // Set some default element properties. + $element['#progress_indicator'] = empty($element['#progress_indicator']) ? 'none' : $element['#progress_indicator']; + $element['#file'] = $fid ? file_load($fid) : FALSE; + $element['#tree'] = TRUE; + + $ajax_settings = array( + 'path' => 'file/ajax/' . implode('/', $element['#parents']) . '/' . $form['form_build_id']['#value'], + 'wrapper' => $element['#id'] . '-ajax-wrapper', + 'method' => 'replace', + 'effect' => 'fade', + 'progress' => array( + 'type' => $element['#progress_indicator'], + 'message' => $element['#progress_message'], + ), + ); + + // Set up the buttons first since we need to check if they were clicked. + $element['upload_button'] = array( + '#name' => implode('_', $element['#parents']) . '_upload_button', + '#type' => 'submit', + '#value' => t('Upload'), + '#validate' => array(), + '#submit' => array('file_managed_file_submit'), + '#ajax' => $ajax_settings, + '#weight' => -5, + ); + + $ajax_settings['progress']['type'] ? $ajax_settings['progress']['type'] == 'bar' : 'throbber'; + $ajax_settings['progress']['message'] = NULL; + $ajax_settings['effect'] = 'none'; + $element['remove_button'] = array( + '#name' => implode('_', $element['#parents']) . '_remove_button', + '#type' => 'submit', + '#value' => t('Remove'), + '#validate' => array(), + '#submit' => array('file_managed_file_submit'), + '#ajax' => $ajax_settings, + '#weight' => -5, + ); + + // Because the output of this field changes depending on the button clicked, + // we need to ask FAPI immediately if the remove button was clicked. + // It's not good that we call this private function, but + // $form_state['clicked_button'] is only available after this #process + // callback is finished. + if (_form_button_was_clicked($element['remove_button'], $form_state)) { + // If it's a temporary file we can safely remove it immediately, otherwise + // it's up to the implementing module to clean up files that are in use. + if ($element['#file'] && $element['#file']->status == 0) { + file_delete($element['#file']); + } + $element['#file'] = FALSE; + $fid = 0; + } + + // Set access on the buttons. + $element['upload_button']['#access'] = empty($fid); + $element['remove_button']['#access'] = !empty($fid); + + $element['fid'] = array( + '#type' => 'hidden', + '#value' => $fid, + ); + + // Add progress bar support to the upload if possible. + if ($element['#progress_indicator'] == 'bar' && $implementation = file_progress_implementation()) { + $upload_progress_key = md5(mt_rand()); + + if ($implementation == 'uploadprogress') { + $element['UPLOAD_IDENTIFIER'] = array( + '#type' => 'hidden', + '#value' => $upload_progress_key, + '#attributes' => array('class' => array('file-progress')), + ); + } + elseif ($implementation == 'apc') { + $element['APC_UPLOAD_PROGRESS'] = array( + '#type' => 'hidden', + '#value' => $upload_progress_key, + '#attributes' => array('class' => array('file-progress')), + ); + } + + // Add the upload progress callback. + $element['upload_button']['#ajax']['progress']['path'] = 'file/progress/' . $upload_progress_key; + } + + // The file upload field itself. + $element['upload'] = array( + '#name' => 'files[' . implode('_', $element['#parents']) . ']', + '#type' => 'file', + '#size' => 22, + '#access' => empty($fid), + '#theme_wrappers' => array(), + '#weight' => -10, + ); + + if ($fid && $element['#file']) { + $element['filename'] = array( + '#type' => 'markup', + '#markup' => theme('file_link', $element['#file']) . ' ', + '#weight' => -10, + ); + } + + // The "accept" attribute is valid XHTML, but not enforced in browsers. + // We use it for our own purposes in our JavaScript validation. + if (isset($element['#upload_validators']['file_validate_extensions'][0])) { + $element['upload']['#attributes']['accept'] = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0]))); + } + + // Prefix and suffix used for AJAX replacement. + $element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">'; + $element['#suffix'] = '</div>'; + + return $element; +} + +/** + * The #value_callback for a managed_file type element. + */ +function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) { + $fid = 0; + + // Find the current value of this field from the form state. + $form_state_fid = $form_state['values']; + foreach ($element['#parents'] as $parent) { + $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0; + } + + if ($element['#extended'] && isset($form_state_fid['fid'])) { + $fid = $form_state_fid['fid']; + } + elseif (is_numeric($form_state_fid)) { + $fid = $form_state_fid; + } + + // Process any input and save new uploads. + if ($input !== FALSE) { + $return = $input; + + // Uploads take priority over all other values. + if ($file = file_managed_file_save_upload($element)) { + $fid = $file->fid; + } + else { + // Check for #filefield_value_callback values. + // Because FAPI does not allow multiple #value_callback values like it + // does for #element_validate and #process, this fills the missing + // functionality to allow File fields to be extended through FAPI. + if (isset($element['#file_value_callbacks'])) { + foreach ($element['#file_value_callbacks'] as $callback) { + $callback($element, $input); + } + } + // Load file if the FID has changed to confirm it exists. + if (isset($input['fid']) && $file = file_load($input['fid'])) { + $fid = $file->fid; + } + } + } + + // If there is no input, set the default value. + else { + if ($element['#extended']) { + $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0; + $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0); + } + else { + $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0; + $return = array('fid' => 0); + } + + // Confirm that the file exists when used as a default value. + if ($default_fid && $file = file_load($default_fid)) { + $fid = $file->fid; + } + } + + $return['fid'] = $fid; + + return $return; +} + +/** + * An #element_validate callback for the managed_file element. + */ +function file_managed_file_validate(&$element, &$form_state) { + // If referencing an existing file, only allow if there are existing + // references. This prevents unmanaged files from being deleted if this + // item were to be deleted. + $clicked_button = end($form_state['clicked_button']['#parents']); + if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) { + if ($file = file_load($element['fid']['#value'])) { + if ($file->status == FILE_STATUS_PERMANENT) { + $reference_count = 0; + foreach (module_invoke_all('file_references', $file) as $module => $references) { + $reference_count += $references; + } + if ($reference_count == 0) { + form_error($element, t('Referencing to the file used in the !name field is not allowed.', array('!name' => $element['#title']))); + } + } + } + else { + form_error($element, t('The file referenced by the !name field does not exist.', array('!name' => $element['#title']))); + } + } + + // Check required property based on the FID. + if ($element['#required'] && empty($element['fid']['#value']) && !in_array($clicked_button, array('upload_button', 'remove_button'))) { + form_error($element['upload'], t('!name field is required.', array('!name' => $element['#title']))); + } + + // Consolidate the array value of this field to a single FID. + if (!$element['#extended']) { + form_set_value($element, $element['fid']['#value'], $form_state); + } +} + +/** + * Submit handler for non-JavaScript uploads. + */ +function file_managed_file_submit($form, &$form_state) { + // Do not redirect and leave the page after uploading a file. This keeps + // all the current form values in place. The file is saved by the + // #value_callback on the form element. + $form_state['redirect'] = FALSE; +} + +/** + * Given a managed_file element, save any files that have been uploaded into it. + * + * @param $element + * The FAPI element whose values are being saved. + * @return + * The file object representing the file that was saved, or FALSE if no file + * was saved. + */ +function file_managed_file_save_upload($element) { + $upload_name = implode('_', $element['#parents']); + if (empty($_FILES['files']['name'][$upload_name])) { + return FALSE; + } + + $destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL; + if (isset($destination) && !file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) { + watchdog('file', 'The upload directory %directory for the file field !name could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $destination, '!name' => $element['#field_name'])); + form_set_error($upload_name, t('The file could not be uploaded.')); + return FALSE; + } + + if (!$file = file_save_upload($upload_name, $element['#upload_validators'], $destination)) { + watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name)); + form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title']))); + return FALSE; + } + + return $file; +} + +/** + * Theme a managed file element. + */ +function theme_file_managed_file($element) { + // This wrapper is required to apply JS behaviors and CSS styling. + $output = ''; + $output .= '<div class="form-managed-file">'; + $output .= drupal_render_children($element); + $output .= '</div>'; + return $output; +} + +/** + * Output a link to a file. + * + * @param $file + * A file object to which the link will be created. + */ +function theme_file_link($file) { + $url = file_create_url($file->uri); + $icon = theme('file_icon', $file); + + // Set options as per anchor format described at + // http://microformats.org/wiki/file-format-examples + $options = array( + 'attributes' => array( + 'type' => $file->filemime . '; length=' . $file->filesize, + ), + ); + + // Use the description as the link text if available. + if (empty($file->data['description'])) { + $link_text = check_plain($file->filename); + } + else { + $link_text = check_plain($file->data['description']); + $options['attributes']['title'] = check_plain($file->filename); + } + + return '<span class="file">' . $icon . ' ' . l($link_text, $url, $options) . '</span>'; +} + +/** + * Return an image with an appropriate icon for the given file. + * + * @param $file + * A file object for which to make an icon. + */ +function theme_file_icon($file) { + $mime = check_plain($file->filemime); + $icon_url = file_icon_url($file); + return '<img class="file-icon" alt="" title="' . $mime . '" src="' . $icon_url . '" />'; +} + +/** + * Given a file object, create a URL to a matching icon. + * + * @param $file + * A file object. + * @param $icon_directory + * (optional) A path to a directory of icons to be used for files. Defaults to + * the value of the "file_icon_directory" variable. + * @return + * A URL string to the icon, or FALSE if an appropriate icon cannot be found. + */ +function file_icon_url($file, $icon_directory = NULL) { + if ($icon_path = file_icon_path($file, $icon_directory)) { + return base_path() . $icon_path; + } + return FALSE; +} + +/** + * Given a file object, create a path to a matching icon. + * + * @param $file + * A file object. + * @param $icon_directory + * (optional) A path to a directory of icons to be used for files. Defaults to + * the value of the "file_icon_directory" variable. + * @return + * A string to the icon as a local path, or FALSE if an appropriate icon could + * not be found. + */ +function file_icon_path($file, $icon_directory = NULL) { + // Use the default set of icons if none specified. + if (!isset($icon_directory)) { + $icon_directory = variable_get('file_icon_directory', drupal_get_path('module', 'file') . '/icons'); + } + + // If there's an icon matching the exact mimetype, go for it. + $dashed_mime = strtr($file->filemime, array('/' => '-')); + $icon_path = $icon_directory . '/' . $dashed_mime . '.png'; + if (file_exists($icon_path)) { + return $icon_path; + } + + // For a few mimetypes, we can "manually" map to a generic icon. + $generic_mime = (string) file_icon_map($file); + $icon_path = $icon_directory . '/' . $generic_mime . '.png'; + if ($generic_mime && file_exists($icon_path)) { + return $icon_path; + } + + // Use generic icons for each category that provides such icons. + foreach (array('audio', 'image', 'text', 'video') as $category) { + if (strpos($file->filemime, $category . '/') === 0) { + $icon_path = $icon_directory . '/' . $category . '-x-generic.png'; + if (file_exists($icon_path)) { + return $icon_path; + } + } + } + + // Try application-octet-stream as last fallback. + $icon_path = $icon_directory . '/application-octet-stream.png'; + if (file_exists($icon_path)) { + return $icon_path; + } + + // No icon can be found. + return FALSE; +} + +/** + * Determine the generic icon MIME package based on a file's MIME type. + * + * @param $file + * A file object. + * @return + * The generic icon MIME package expected for this file. + */ +function file_icon_map($file) { + switch ($file->filemime) { + // Word document types. + case 'application/msword': + case 'application/vnd.ms-word.document.macroEnabled.12': + case 'application/vnd.oasis.opendocument.text': + case 'application/vnd.oasis.opendocument.text-template': + case 'application/vnd.oasis.opendocument.text-master': + case 'application/vnd.oasis.opendocument.text-web': + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + case 'application/vnd.stardivision.writer': + case 'application/vnd.sun.xml.writer': + case 'application/vnd.sun.xml.writer.template': + case 'application/vnd.sun.xml.writer.global': + case 'application/vnd.wordperfect': + case 'application/x-abiword': + case 'application/x-applix-word': + case 'application/x-kword': + case 'application/x-kword-crypt': + return 'x-office-document'; + + // Spreadsheet document types. + case 'application/vnd.ms-excel': + case 'application/vnd.ms-excel.sheet.macroEnabled.12': + case 'application/vnd.oasis.opendocument.spreadsheet': + case 'application/vnd.oasis.opendocument.spreadsheet-template': + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': + case 'application/vnd.stardivision.calc': + case 'application/vnd.sun.xml.calc': + case 'application/vnd.sun.xml.calc.template': + case 'application/vnd.lotus-1-2-3': + case 'application/x-applix-spreadsheet': + case 'application/x-gnumeric': + case 'application/x-kspread': + case 'application/x-kspread-crypt': + return 'x-office-spreadsheet'; + + // Presentation document types. + case 'application/vnd.ms-powerpoint': + case 'application/vnd.ms-powerpoint.presentation.macroEnabled.12': + case 'application/vnd.oasis.opendocument.presentation': + case 'application/vnd.oasis.opendocument.presentation-template': + case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': + case 'application/vnd.stardivision.impress': + case 'application/vnd.sun.xml.impress': + case 'application/vnd.sun.xml.impress.template': + case 'application/x-kpresenter': + return 'x-office-presentation'; + + // Compressed archive types. + case 'application/zip': + case 'application/x-zip': + case 'application/stuffit': + case 'application/x-stuffit': + case 'application/x-7z-compressed': + case 'application/x-ace': + case 'application/x-arj': + case 'application/x-bzip': + case 'application/x-bzip-compressed-tar': + case 'application/x-compress': + case 'application/x-compressed-tar': + case 'application/x-cpio-compressed': + case 'application/x-deb': + case 'application/x-gzip': + case 'application/x-java-archive': + case 'application/x-lha': + case 'application/x-lhz': + case 'application/x-lzop': + case 'application/x-rar': + case 'application/x-rpm': + case 'application/x-tzo': + case 'application/x-tar': + case 'application/x-tarz': + case 'application/x-tgz': + return 'package-x-generic'; + + // Script file types. + case 'application/ecmascript': + case 'application/javascript': + case 'application/mathematica': + case 'application/vnd.mozilla.xul+xml': + case 'application/x-asp': + case 'application/x-awk': + case 'application/x-cgi': + case 'application/x-csh': + case 'application/x-m4': + case 'application/x-perl': + case 'application/x-php': + case 'application/x-ruby': + case 'application/x-shellscript': + case 'text/vnd.wap.wmlscript': + case 'text/x-emacs-lisp': + case 'text/x-haskell': + case 'text/x-literate-haskell': + case 'text/x-lua': + case 'text/x-makefile': + case 'text/x-matlab': + case 'text/x-python': + case 'text/x-sql': + case 'text/x-tcl': + return 'text-x-script'; + + // HTML aliases. + case 'application/xhtml+xml': + return 'text-html'; + + // Executable types. + case 'application/x-macbinary': + case 'application/x-ms-dos-executable': + case 'application/x-pef-executable': + return 'application-x-executable'; + + default: + return FALSE; + } +} + +/** + * @defgroup file-module-api File module public API functions + * @{ + * These functions may be used to determine if and where a file is in use. + */ + +/** + * Return an array of file fields in an bundle or by field name. + * + * @param $bundle_type + * (optional) The bundle type on which to filter the list of fields. In the + * case of nodes, this is the node type. + * @param $field + * (optional) A field array or name on which to filter the list. + */ +function file_get_field_list($bundle_type = NULL, $field = NULL) { + // Build the list of fields to be used for retrieval. + if (isset($field)) { + if (is_string($field)) { + $field = field_info_field($field); + } + $fields = array($field['field_name'] => $field); + } + elseif (isset($bundle_type)) { + $instances = field_info_instances($bundle_type); + $fields = array(); + foreach ($instances as $field_name => $instance) { + $fields[$field_name] = field_info_field($field_name); + } + } + else { + $fields = field_info_fields(); + } + + // Filter down the list to just file fields. + foreach ($fields as $key => $field) { + if ($field['type'] != 'file') { + unset($fields[$key]); + } + } + + return $fields; +} + +/** + * Count the number of times the file is referenced within a field. + * + * @param $file + * A file object. + * @param $field + * Optional. The CCK field array or field name as a string. + * @return + * An integer value. + */ +function file_get_file_reference_count($file, $field = NULL) { + $fields = file_get_field_list(NULL, $field); + $types = field_info_fieldable_types(); + $reference_count = 0; + + foreach ($fields as $field) { + // TODO: Use a more efficient mechanism rather than actually retrieving + // all the references themselves, such as using a COUNT() query. + $references = file_get_file_references($file, $field); + foreach ($references as $obj_type => $type_references) { + $reference_count += count($type_references); + } + + // If a field_name is present in the file object, the file is being deleted + // from this field. + if (isset($file->file_field_name) && $field['field_name'] == $file->file_field_name) { + // If deleting the entire piece of content, decrement references. + if (isset($file->file_field_type) && isset($file->file_field_id)) { + if ($file->file_field_type == $obj_type) { + $info = field_info_fieldable_types($obj_type); + $id = $types[$obj_type]['object keys']['id']; + foreach ($type_references as $reference) { + if ($file->file_field_id == $reference->$id) { + $reference_count--; + } + } + } + } + // Otherwise we're just deleting a single reference in this field. + else { + $reference_count--; + } + } + } + + return $reference_count; +} + + +/** + * Get a list of references to a file by bundle and ID. + * + * @param $file + * A file object. + * @param $field + * (optional) A field array to be used for this check. + * @param $age + * (optional) A constant that specifies which references to count. Use + * FIELD_LOAD_REVISION to retrieve all references within all revisions or + * FIELD_LOAD_CURRENT to retrieve references only in the current revisions. + * @return + * An integer value. + */ +function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION) { + $references = drupal_static(__FUNCTION__, array()); + $fields = file_get_field_list(NULL, $field); + + foreach ($fields as $field_name => $file_field) { + if (!isset($references[$field_name])) { + // Get each time this file is used within a field. + $cursor = 0; + $references[$field_name] = field_attach_query($file_field['id'], array(array('fid', $file->fid)), FIELD_QUERY_NO_LIMIT, $cursor, $age); + } + } + + return isset($field) ? $references[$field['field_name']] : $references; +} + +/** + * @} End of "defgroup file-module-api". + */ |