'filefield_js', 'page arguments' => array(3, 4, 5, 'filefield_file_upload_js'), 'access callback' => 'filefield_edit_access', 'access arguments' => array(3), 'type' => MENU_CALLBACK, ); $items['filefield/js/delete/%/%/%'] = array( 'page callback' => 'filefield_js', 'page arguments' => array(3, 4, 5, 'filefield_file_edit_delete_js'), 'access callback' => 'filefield_edit_access', 'access arguments' => array(3), 'type' => MENU_CALLBACK, ); return $items; } /** * Access callback for the JavaScript upload and deletion AHAH callbacks. * The content_permissions module provides nice fine-grained permissions for * us to check, so we can make sure that the user may actually edit the file. */ function filefield_edit_access($field_name) { if (module_exists('content_permissions')) { return user_access('edit '. $field_name); } // No content permissions to check, so let's fall back to a more general permission. return user_access('access content'); } /** * Access callback that checks if the current user may view the filefield. */ function filefield_view_access($field_name) { if (module_exists('content_permissions')) { return user_access('view '. $field_name); } // No content permissions to check, so let's fall back to a more general permission. return user_access('access content'); } /** * Implementation of hook_elements(). */ function filefield_elements() { $elements = array(); $elements['filefield_file_upload'] = array( '#input' => TRUE, '#process' => array('filefield_file_upload_process'), '#element_validate' => array(), // later filled with 'filefield_file_upload_validate' '#value_callback' => 'filefield_file_upload_value', '#replaced_file' => NULL, ); $elements['filefield_file_edit'] = array( '#input' => TRUE, '#process' => array('filefield_file_edit_process'), '#value_callback' => 'filefield_file_edit_value', ); $elements['filefield_generic_edit'] = array( '#input' => TRUE, '#process' => array('filefield_generic_edit_process'), ); return $elements; } /** * Implementation of hook_theme(). */ function filefield_theme() { return array( 'filefield_container_item' => array( 'arguments' => array('element' => NULL), ), 'filefield_file_upload' => array( 'arguments' => array('element' => NULL), ), 'filefield_file_edit' => array( 'arguments' => array('element' => NULL), ), 'filefield_generic_edit' => array( 'arguments' => array('element' => NULL), ), 'filefield_formatter_default' => array( 'arguments' => array('element' => NULL), ), 'filefield' => array( 'arguments' => array('file' => NULL), ), 'filefield_icon' => array( 'arguments' => array('file' => NULL), ), ); } /** * Implementation of hook_field_info(). */ function filefield_field_info() { return array( 'file' => array( 'label' => 'File', 'description' => t('Store an arbitrary file.'), ), ); } /** * Implementation of hook_field_settings(). */ function filefield_field_settings($op, $field) { switch ($op) { case 'form': $form = array(); $form['force_list'] = array( '#type' => 'checkbox', '#title' => t('Always list files'), '#default_value' => isset($field['force_list']) ? $field['force_list'] : 0, '#description' => t('If enabled, the "List" checkbox will be hidden and files are always shown. Otherwise, the user can choose for each file whether it should be listed or not.'), ); return $form; case 'validate': break; case 'save': return array('force_list'); case 'database columns': $columns = array( 'fid' => array('type' => 'int', 'not null' => FALSE), 'description' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE), 'list' => array('type' => 'int', 'size' => 'tiny', 'not null' => FALSE), ); return $columns; case 'views data': $data = content_views_field_views_data($field); $db_info = content_database_info($field); $table_alias = content_views_tablename($field); // By defining the relationship, we already have a "Has file" filter // plus all the filters that Views already provides for files. // No need for having a filter by ourselves. unset($data[$table_alias][$field['field_name'] .'_fid']['filter']); // Add a relationship for related file. $data[$table_alias][$field['field_name'] .'_fid']['relationship'] = array( 'base' => 'files', 'field' => $db_info['columns']['fid']['column'], 'handler' => 'views_handler_relationship', ); return $data; } } /** * Implementation of hook_content_is_empty(). * * The result of this determines whether content.module will save * the value of the field. */ function filefield_content_is_empty($item, $field) { if (empty($item['fid'])) { return TRUE; } return FALSE; } /** * Implementation of hook_field(). */ function filefield_field($op, $node, $field, &$items, $teaser, $page) { $field_name = $field['field_name']; switch ($op) { // Called after content.module loads default data. case 'load': if (empty($items)) { return array(); } foreach ($items as $delta => $item) { // Despite hook_content_is_empty(), CCK still doesn't filter out // empty items from $op = 'load', so we need to do that ourselves. if (empty($item['fid']) || !($file = field_file_load($item['fid']))) { unset($items[$delta]); } else { $items[$delta] = array_merge($item, $file); } } $items = array_values($items); // compact deltas return array($field_name => $items); case 'insert': case 'update': foreach ($items as $delta => $item) { $items[$delta] = field_file_save($node, $item); // Remove items from the array if they have been deleted. if (empty($items[$delta])) { unset($items[$delta]); } } $items = array_values($items); // compact deltas break; case 'presave': // Extract previous (permanent) files from the items array that have been // deleted or replaced, so that insert/update can remove them properly. foreach ($items as $delta => $item) { if (!empty($item['replaced_file'])) { $items[] = $item['replaced_file']; } } break; case 'delete revision': foreach ($items as $delta => $item) { if (field_file_delete($item)) { $items[$delta] = array(); } } $items = array_values($items); // compact deltas break; case 'delete': foreach ($items as $delta => $item) { field_file_delete($item); } break; case 'sanitize': foreach ($items as $delta => $item) { // Cleanup $items during node preview. if (empty($item['fid']) || !empty($item['delete'])) { unset($items[$delta]); } else { // Load the complete file if a filepath is not available. if (!empty($item['fid']) && empty($item['filepath'])) { $items[$delta] = array_merge($item, field_file_load($item['fid'])); } // Add nid so formatters can create a link to the node. $items[$delta]['nid'] = $node->nid; } } break; } } /** * Implementation of hook_widget_info(). */ function filefield_widget_info() { return array( 'filefield_combo' => array( 'label' => 'File', 'field types' => array('file'), 'multiple values' => CONTENT_HANDLE_CORE, 'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM), ), ); } /** * Implementation of hook_widget_settings(). */ function filefield_widget_settings($op, $widget) { switch ($op) { case 'form': $form = array(); $form['file_extensions'] = array ( '#type' => 'textfield', '#title' => t('Permitted upload file extensions'), '#default_value' => is_string($widget['file_extensions']) ? $widget['file_extensions'] : 'txt', '#size' => 64, '#description' => t('Extensions a user can upload to this field. Separate extensions with a space and do not include the leading dot. Leaving this blank will allow users to upload a file with any extension.'), ); $form['file_path'] = array( '#type' => 'textfield', '#title' => t('File path'), '#default_value' => is_string($widget['file_path']) ? $widget['file_path'] : '', '#description' => t('Optional subdirectory within the "%dir" directory where files will be stored. Do not include trailing slash.', array('%dir' => variable_get('file_directory_path', 'files'))), '#element_validate' => array('_filefield_widget_settings_file_path_validate'), ); if (module_exists('token')) { $form['file_path']['#suffix'] = theme('token_help', 'user'); } // Let extension modules add their settings to the form. foreach (module_implements('filefield_widget_settings') as $module) { $function = $module .'_filefield_widget_settings'; $function('form_alter', $widget, $form); } return $form; case 'validate': module_invoke_all('filefield_widget_settings', $op, $widget, NULL); break; case 'save': $core_settings = array('file_extensions', 'file_path'); $additional_settings = module_invoke_all( 'filefield_widget_settings', $op, $widget, NULL ); return array_merge($core_settings, $additional_settings); } } function _filefield_widget_settings_file_path_validate($element, &$form_state) { // Strip slashes from the beginning and end of $widget['file_path'] $form_state['values']['file_path'] = trim($form_state['values']['file_path'], '\\/'); } /** * Implementation of hook_widget(). */ function filefield_widget(&$form, &$form_state, $field, $items, $delta = 0) { drupal_add_css(drupal_get_path('module', 'filefield') .'/filefield.css'); if (!$file = field_file_load($items[$delta]['fid'])) { return filefield_file_upload_form($form, $form_state, $field, $delta, $items[$delta]); } $file = array_merge($items[$delta], $file); return filefield_file_edit_form($form, $form_state, $field, $delta, $file); } /** * Render either an upload or edit container item so that the children elements * always appear inside a nice table, whatever $field['multiple'] might be. */ function theme_filefield_container_item($element) { $field = $element['#field']; $children = !empty($element['#children']) ? $element['#children'] : ''; $children = '
'. $children .'
'; // CCK renders a nice table for multiple-value fields, that's just fine as is. if ($field['multiple']) { return $children; } // If the field is single-value, we still want to have a table, for the looks. $header = array(); $rows = array(array($children)); $attributes = array('class' => 'filefield-file-container-table'); $table = theme('table', $header, $rows, $attributes); return theme('form_element', $element, $table); } /** * The filefield widget for not (yet) existing files. */ function filefield_file_upload_form(&$form, &$form_state, $field, $delta, $item = NULL) { $form['#attributes']['enctype'] = 'multipart/form-data'; drupal_add_js(drupal_get_path('module', 'filefield') .'/filefield.js'); $id = 'filefield-'. $field_name_css .'-'. $delta .'-form'; $replaced_file = (isset($item) && isset($item['replaced_file'])) ? $item['replaced_file'] : NULL; $widget = array( '#type' => 'filefield_file_upload', '#field' => $field, '#delta' => $delta, '#replaced_file' => $replaced_file, '#prefix' => '
', '#suffix' => '
', ); // Buttons inside custom form elements are not registered by the Forms API, // so we make the "Upload" button a regular child element and not a part // of the filefield_file_upload widget. $widget[$field['field_name'] .'_'. $delta .'_upload'] = array( '#name' => $field['field_name'] .'_'. $delta .'_upload', '#type' => 'submit', '#value' => t('Upload'), '#submit' => array('filefield_file_upload_submit'), // without JavaScript '#ahah' => array( // with JavaScript 'path' => 'filefield/js/upload/'. $field['field_name'] .'/'. $field['type_name'] .'/'. $delta, 'wrapper' => $id, 'method' => 'replace', 'effect' => 'fade', ), '#weight' => 10, '#field' => $field, '#delta' => $delta, ); return $widget; } /** * The 'process' callback for 'filefield_file_upload' form elements. * Called after defining the form and while building it, transforms the * barebone element array into a file selection widget. */ function filefield_file_upload_process($element, $edit, &$form_state, $form) { // Before the element user gets to do his validation, make sure we do ours. array_unshift($element['#element_validate'], 'filefield_file_upload_validate'); $field = $element['#field']; $field_name = $field['field_name']; // Construct the upload description out of user supplied text, // maximum upload file size, and (optionally) allowed extensions. $upload_description = t('Maximum file size: !size.', array( '!size' => format_size(file_upload_max_size()), )); if (!empty($field['widget']['file_extensions'])) { $upload_description .= ' ' . t('Allowed extensions: %ext.', array( '%ext' => $field['widget']['file_extensions'], )); } $upload_name = $field_name .'_'. $element['#delta']; $element[$upload_name] = array( '#type' => 'file', '#title' => t('Attach new file'), '#description' => $upload_description, '#weight' => -1, // Emulate how FAPI normalizes the _FILES array since this won't go through form_builder '#name' => 'files['. $upload_name .']', ); // User 1 may even upload files with extensions that are not allowed. // (At least, that's how core's file_validate_extensions() thinks about it.) // So only add the JavaScript extension check for other users. global $user; if ($user->uid != 1) { $element[$upload_name]['#attributes'] = array( 'accept' => str_replace(' ', '|', trim($field['widget']['file_extensions'])) ); } return $element; } /** * Theme function for the file upload container element. */ function theme_filefield_file_upload($element) { return theme('filefield_container_item', $element); } /** * Value callback for 'filefield_upload' form elements. * Uploads and validates a file if one has been specified, * and returns the fid of that file as result value. */ function filefield_file_upload_value($element, $edit = FALSE) { return empty($element['#value']) ? array('fid' => 0, 'replaced_file' => $element['#replaced_file']) : $element['#value']; } /** * The 'validate' callback for 'duration_combo' form elements. * Called after values are assigned, before form validate and submit are called. */ function filefield_file_upload_validate($element, &$form_state) { if (!empty($element['#required']) && empty($element['#value']['fid'])) { form_error($element, t('You need to upload a file to the %field field.', array( '%field' => $element['#field']['widget']['label'] ))); } } /** * Submit callback for the "Upload" button next to each file upload field. */ function filefield_file_upload_submit($form, &$form_state) { $field = $form_state['clicked_button']['#field']; $delta = $form_state['clicked_button']['#delta']; filefield_file_upload($form_state, $field, $delta); // Rebuild the form with the new uploaded-file state (hopefully). node_form_submit_build_node($form, $form_state); } /** * Form callback for the "Upload" button with JavaScript enabled, * invoked by filefield_js(). Uploads a file to the given field and delta. */ function filefield_file_upload_js(&$form, &$form_state, $field, $delta) { filefield_file_upload($form_state, $field, $delta); } function filefield_file_upload(&$form_state, $field, $delta) { $field_name = $field['field_name']; $file = &$form_state['values'][$field_name][$delta]; $replaced_file = $file['replaced_file']; if (module_exists('token')) { global $user; $widget_file_path = token_replace($field['widget']['file_path'], 'user', $user); } else { $widget_file_path = $field['widget']['file_path']; } $validators = array( 'file_validate_extensions' => array($field['widget']['file_extensions']), ); $upload_name = $field_name .'_'. $delta; $complete_file_path = file_directory_path() .'/'. $widget_file_path; if (!filefield_check_directory($widget_file_path, $upload_name)) { watchdog('file', 'The upload directory %directory for the file field %field (content type %type) 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' => $widget_file_path, '%field' => $field_name, '%type' => $field['type_name'])); $file = array('fid' => 0, 'replaced_file' => $replaced_file); return $file; } if (!$file = file_save_upload($upload_name, $validators, $complete_file_path)) { watchdog('file', 'The file %file could not be saved as addition to the file field %field (content type %type). This can be a consequence of the file failing validation, or if it can\'t be moved to the file directory, or whatever reason the file_save_upload() function comes up with. No further information is available to the filefield module, but if you\'re lucky then that function left one or more hints in the log as well (directly before this log entry).', array('%file' => $complete_file_path, '%field' => $field_name, '%type' => $field['type_name'])); $file = array('fid' => 0, 'replaced_file' => $replaced_file); return $file; } $file_default_properties = array( 'list' => 1, 'description' => $file->filename, ); $file = array_merge($file_default_properties, (array) $file); $file['replaced_file'] = $replaced_file; return $file; } /** * The filefield widget for previously uploaded files. */ function filefield_file_edit_form(&$form, &$form_state, $field, $delta, $file) { $field_name_css = str_replace('_', '-', $field['field_name']); $id = 'filefield-'. $field_name_css .'-'. $delta .'-form'; $classes = array( 'filefield-'. $field_name_css .'-form', 'filefield-file-form', ); $widget = array( '#type' => 'filefield_file_edit', '#default_value' => $file, '#field' => $field, '#prefix' => '
', '#suffix' => '
', ); // Buttons inside custom form elements are not registered by the Forms API, // so we make the "Delete" button a regular child element and not a part // of the filefield_file_upload widget. $widget['flags'] = array( '#type' => 'markup', '#value' => '', '#prefix' => '
', '#suffix' => '
', ); $widget['flags'][$field['field_name'] .'_'. $delta .'_delete'] = array( '#name' => $field['field_name'] .'_'. $delta .'_delete', '#type' => 'submit', '#value' => t('Delete'), '#submit' => array('filefield_file_edit_delete_submit'), // without JavaScript '#ahah' => array( // with JavaScript 'path' => 'filefield/js/delete/'. $field['field_name'] .'/'. $field['type_name'] .'/'. $delta, 'wrapper' => $id, 'method' => 'replace', 'effect' => 'fade', ), '#field' => $field, '#delta' => $delta, '#file' => $file, ); // Only show the list checkbox if files are not forced to be listed. if (!$field['force_list']) { $widget['flags']['list'] = array( '#type' => 'checkbox', '#title' => t('List'), '#default_value' => $file['list'], ); } $edit_widget_info = filefield_widget_for_file($field, $file); $widget['edit'] = array( '#type' => $edit_widget_info['form element'], '#field' => $field, '#delta' => $delta, '#default_value' => $file, '#prefix' => '
', '#suffix' => '
', ); return $widget; } /** * Theme function for the file edit container element. */ function theme_filefield_file_edit($element) { return theme('filefield_container_item', $element); } /** * Custom value callback for file edit widgets, so that we don't need to rely * on a tree structure but can assemble the file to our likings. */ function filefield_file_edit_value($element, $edit = FALSE) { $file = $element['#default_value']; if (!is_array($edit)) { return $file; } $file_fixed_properties = array( 'list' => isset($edit['flags']['list']) ? $edit['flags']['list'] : $file['list'], 'delete' => 0, 'fid' => $file['fid'], 'uid' => $file['uid'], 'status' => $file['status'], 'filename' => $file['filename'], 'filepath' => $file['filepath'], 'filemime' => $file['filemime'], 'filesize' => $file['filesize'], 'timestamp' => $file['timestamp'], ); if (is_array($edit['edit'])) { $file = array_merge($file, $edit['edit']); } $file = array_merge($file, $file_fixed_properties); // Also merge in other values that might come from other child form elements, // like the '_weight' property that CCK adds to this field. unset($edit['flags']); unset($edit['edit']); $file = array_merge($edit, $file); return $file; } /** * Submit callback for the "Delete" button next to each file item. */ function filefield_file_edit_delete_submit($form, &$form_state) { $field = $form_state['clicked_button']['#field']; $delta = $form_state['clicked_button']['#delta']; filefield_file_edit_delete($form_state, $field, $delta); // Rebuild the form with the new deleted-file state. node_form_submit_build_node($form, $form_state); } /** * Form callback for the "Delete" button with JavaScript enabled, * invoked by filefield_js(). Marks the file in the given field and delta * as deleted, or deletes it right away (depending on the context). */ function filefield_file_edit_delete_js(&$form, &$form_state, $field, $delta) { filefield_file_edit_delete($form_state, $field, $delta); } /** * Update the form state so that the file for the given field and delta * is marked as deleted. */ function filefield_file_edit_delete(&$form_state, $field, $delta) { $field_name = $field['field_name']; $file = &$form_state['values'][$field_name][$delta]; if (isset($file['status']) && $file['status'] == FILE_STATUS_PERMANENT) { $file['delete'] = 1; $file = array( 'fid' => 0, 'replaced_file' => $file, ); } else { // temporary file, get rid of it before it's even saved $empty_file = array( 'fid' => 0, 'replaced_file' => $file['replaced_file'], // remember permanent files from before ); field_file_delete($file); $file = $empty_file; } return $file; } /** * Shared AHAH callback for uploads and deletions. It just differs in a few * unimportant details (what happens to the file, and which form is used as * a replacement) so these details are taken care of by a form callback. */ function filefield_js($field_name, $type_name, $delta, $form_callback) { $field = content_fields($field_name, $type_name); if (empty($field) || empty($_POST['form_build_id'])) { // Invalid request. print drupal_to_js(array('data' => '')); exit; } // Build the new form. $form_state = array('submitted' => FALSE); $form_build_id = $_POST['form_build_id']; $form = form_get_cache($form_build_id, $form_state); if (!$form) { // Invalid form_build_id. print drupal_to_js(array('data' => '')); exit; } // form_get_cache() doesn't yield the original $form_state, // but form_builder() does. Needed for retrieving the file array. $built_form = $form; $built_form_state = $form_state; $built_form = form_builder($_POST['form_id'], $built_form, $built_form_state); // Clean ids, so that the same element doesn't get a different element id // when rendered once more further down. form_clean_id(NULL, TRUE); // Perform the action for this AHAH callback. $form_callback($built_form, $built_form_state, $field, $delta); // Ask CCK for the replacement form element. Going through CCK gets us // the benefit of nice stuff like '#required' merged in correctly. module_load_include('inc', 'content', 'includes/content.node_form'); $field_element = content_field_form($form, $built_form_state, $field, $delta); $delta_element = $field_element[$field_name][0]; // there's only one element in there // Add the new element at the right place in the form. if (module_exists('fieldgroup') && ($group_name = _fieldgroup_field_get_group($type_name, $field_name))) { $form[$group_name][$field_name][$delta] = $delta_element; } else { $form[$field_name][$delta] = $delta_element; } // Write the (unbuilt, updated) form back to the form cache. form_set_cache($form_build_id, $form, $form_state); // Render the form for output. $form += array( '#post' => $_POST, '#programmed' => FALSE, ); drupal_alter('form', $form, array(), 'filefield_js'); $form_state = array('submitted' => FALSE); $form = form_builder('filefield_js', $form, $form_state); $field_form = empty($group_name) ? $form[$field_name] : $form[$group_name][$field_name]; $output = theme('status_messages') . drupal_render($field_form[$delta]); // AHAH is not being nice to us and doesn't know the "other" button (that is, // either "Upload" or "Delete") yet. Which in turn causes it not to attach // AHAH behaviours after replacing the element. So we need to tell it first. $javascript = drupal_add_js(NULL, NULL); if (isset($javascript['setting'])) { $output .= ''; } // For some reason, file uploads don't like drupal_json() with its manual // setting of the text/javascript HTTP header. So use this one instead. print drupal_to_js(array('status' => TRUE, 'data' => $output)); exit; } /** * Determine which widget will be used for displaying the edit form * for the given file. */ function filefield_widget_for_file($field, $file) { $file_widget_info = module_invoke_all('file_widget_info'); drupal_alter('file_widget_info', $file_widget_info); $compatible_widget_info = array(); foreach ($file_widget_info as $element_name => $info) { $info['form element'] = $element_name; $priority_callback = $info['priority callback']; $priority = $priority_callback($field, $file); if (!is_numeric($priority)) { continue; // this widget is not interested in our file } $compatible_widget_info[$priority] = $info; } // Return the element with highest priority. krsort($compatible_widget_info); return reset($compatible_widget_info); } /** * Implementation of hook_file_widget_info(). */ function filefield_file_widget_info() { return array( 'filefield_generic_edit' => array( 'priority callback' => 'filefield_generic_edit_priority', ), ); } /** * Priority callback for the 'filefield_generic' widget: * Really low priority for all files, as this is the most basic fallback. */ function filefield_generic_edit_priority($field, $file) { return -1; } /** * The 'process' callback for 'filefield_generic_edit' form elements. * Called after defining the form and while building it, transforms the * barebone element array into an icon and and a text field for editing * the file description. */ function filefield_generic_edit_process($element, $edit, &$form_state, $form) { $field = $element['#field']; $delta = $element['#delta']; $file = $element['#value']; $url = file_create_url($file['filepath']); $prefix = isset($element['#prefix']) ? $element['#prefix'] : ''; $suffix = isset($element['#suffix']) ? $element['#suffix'] : ''; $element['#prefix'] = $prefix .'
'; $element['#suffix'] = '
'. $suffix; $element['icon'] = array( '#type' => 'markup', '#value' => theme('filefield_icon', $file), ); $element['description'] = array( '#type' => 'textfield', '#default_value' => (strlen($file['description'])) ? $file['description'] : $file['filename'], '#maxlength' => 256, '#description' => t('Size: !size, URL: !url', array( '!size' => format_size($file['filesize']), '!url' => l($url, $url), )), '#required' => TRUE, '#prefix' => '
', '#suffix' => '
', ); return $element; } /** * Theme function for the 'filefield_generic_edit' form element. */ function theme_filefield_generic_edit($element) { return theme('form_element', $element, $element['#children']); } /* @todo: port hook_filefield() to the current version function _filefield_widget_prepare_form_values($node, $field, &$items) { $field_name = $field['field_name']; // Attach new files. if ($file = file_check_upload($field_name .'_upload')) { $file = (array)$file; // let extended validation from other module happen so we get all error messages. // if you implement hook_filefield_file() return FALSE to stop the upload. if (empty($errors)) { $errors = module_invoke_all('filefield', 'file_validate', $node, $field, $file); } // let modules massage act on the file. foreach(module_implements('filefield') as $module) { $function = $module .'_filefield'; $function('file_prepare', $node, $field, $file); } } }*/ /* @todo: do we still need those labels? kinda cumbersome with CCK controlling all of our multiple-value stuff. function _filefield_widget_form($node, $field, &$items) { $form[$field_name] = array( '#type' => 'fieldset', '#title' => t($field['widget']['label']), '#description' => t('Changes made to the attachments are not permanent until you save this post.'), ); return $form; } */ /** * Implementation of hook_field_formatter_info(). */ function filefield_field_formatter_info() { return array( 'default' => array( 'label' => t('Default'), 'field types' => array('file'), 'multiple values' => CONTENT_HANDLE_CORE, ), ); } /** * Theme function for the 'default' filefield field formatter. */ function theme_filefield_formatter_default($element) { $file = $element['#item']; if (!$file['fid'] || !filefield_view_access($element['#field_name'])) { return ''; } $field = content_fields($element['#field_name']); if($field['force_list']) { $file['list'] = 1; // always show the files if that option is enabled } drupal_add_css(drupal_get_path('module', 'filefield') .'/filefield.css'); return theme('filefield', $file); } function theme_filefield_icon($file) { $dashed_mime = check_plain(strtr($file['filemime'], array('/' => '-'))); if ($icon_url = filefield_icon_url($file)) { $icon = ''; } return '
'. $icon .'
'; } function theme_filefield($file) { if (is_file($file['filepath']) && $file['list']) { $path = ($file['fid'] == 'upload') ? file_create_filename($file['filename'], file_create_path($field['widget']['file_path'])) : $file['filepath']; $icon = theme('filefield_icon', $file); $url = file_create_url($path); $desc = $file['description']; return '
'. $icon . l($desc, $url) .'
'; } return ''; } /** * Implementation of hook_file_download(). Yes, *that* hook that causes * any attempt for file upload module interoperability to fail spectacularly. */ function filefield_file_download($file) { $file = file_create_path($file); $result = db_query("SELECT * FROM {files} WHERE filepath = '%s'", $file); if (!$file = db_fetch_object($result)) { // We don't really care about this file. return; } // Find out if any filefield contains this file, and if so, which field // and node it belongs to. Required for later access checking. $cck_files = array(); foreach (content_fields() as $field) { if ($field['type'] == 'file') { $db_info = content_database_info($field); $table = $db_info['table']; $fid_column = $db_info['columns']['fid']['column']; $columns = array('vid', 'nid'); foreach ($db_info['columns'] as $property_name => $column_info) { $columns[] = $column_info['column'] .' AS '. $property_name; } $result = db_query("SELECT ". implode(', ', $columns) ." FROM {". $table ."} WHERE ". $fid_column ." = %d", $file->fid); while ($content = db_fetch_array($result)) { $content['field'] = $field; $cck_files[$field['field_name']][$content['vid']] = $content; } } } // If no filefield item is involved with this file, we don't care about it. if (empty($cck_files)) { return; } // If any node includes this file but the user may not view this field, // then deny the download. foreach ($cck_files as $field_name => $field_files) { if (!filefield_view_access($field_name)) { return -1; } } // So the overall field view permissions are not denied, but if access is // denied for a specific node containing the file, deny the download as well. // It's probably a little too restrictive, but I can't think of a // better way at the moment. Input appreciated. // (And yeah, node access checks also include checking for 'access content'.) $nodes = array(); foreach ($cck_files as $field_name => $field_files) { foreach ($field_files as $revision_id => $content) { // Checking separately for each revision is probably not the best idea - // what if 'view revisions' is disabled? So, let's just check for the // current revision of that node. if (isset($nodes[$content['nid']])) { continue; // don't check the same node twice } $node = node_load($content['nid']); if (!node_access('view', $node)) { // You don't have permission to view the node this file is attached to. return -1; } $nodes[$content['nid']] = $node; } } // Well I guess you can see this file. $name = mime_header_encode($file->filename); $type = mime_header_encode($file->filemime); // Serve images and text inline for the browser to display rather than download. $disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment'; return array( 'Content-Type: '. $type .'; name='. $name, 'Content-Length: '. $file->filesize, 'Content-Disposition: '. $disposition .'; filename='. $name, 'Cache-Control: private', ); } /** * Create the file directory relative to the 'files' dir recursively for every * directory in the path. * * @param $directory * The directory path under files to check, such as 'photo/path/here' * @param $form_item * An optional string containing the name of a form item that any errors * will be attached to. (See field_file_check_directory() for more details.) */ function filefield_check_directory($directory, $form_item = NULL) { $directory = field_file_strip_path($directory); foreach (explode('/', $directory) as $dir) { $dirs[] = $dir; $path = file_create_path(implode($dirs, '/')); if (!field_file_check_directory($path, FILE_CREATE_DIRECTORY, $form_item)) { watchdog('filefield', t('FileField failed to create directory (%d) at (%p).', array('%d' => $directory, '%p' => $path)), WATCHDOG_ERROR); return FALSE; } } return TRUE; } /** * Implementation of hook_token_list(): * Provide a user readable list of filefield tokens. */ function filefield_token_list($type = 'all') { if ($type == 'field' || $type == 'all') { $tokens = array(); $tokens['file']['fid'] = t("File ID"); $tokens['file']['description'] = t("File description"); $tokens['file']['filename'] = t("File name"); $tokens['file']['filepath'] = t("File path"); $tokens['file']['filemime'] = t("File MIME type"); $tokens['file']['filesize'] = t("File size (in bytes)"); $tokens['file']['filesize_formatted'] = t("File size (pretty printed)"); $tokens['file']['view'] = t("Fully formatted HTML file tag"); return $tokens; } } /** * Implementation of hook_token_values(): * Provide the concrete token values for a given file item. */ function filefield_token_values($type, $object = NULL) { if ($type == 'field') { $item = $object[0]; $tokens['fid'] = $item['fid']; $tokens['description'] = $item['description']; $tokens['filename'] = $item['filename']; $tokens['filepath'] = $item['filepath']; $tokens['filemime'] = $item['filemime']; $tokens['filesize'] = $item['filesize']; $tokens['filesize_formatted'] = format_size($item['filesize']); $tokens['view'] = $item['view']; return $tokens; } } /** * Determine the most appropriate icon for the given file's mimetype. * * @return The URL of the icon image file, or FALSE if no icon could be found. */ function filefield_icon_url($file) { global $base_url; $theme = variable_get('filefield_icon_theme', 'protocons'); if ($iconpath = _filefield_icon_path($file, $theme)) { return $base_url .'/'. $iconpath; } return FALSE; } function _filefield_icon_path($file, $theme = 'protocons') { // If there's an icon matching the exact mimetype, go for it. $dashed_mime = strtr($file['filemime'], array('/' => '-')); if ($iconpath = _filefield_create_icon_path($dashed_mime, $theme)) { return $iconpath; } // For a couple of mimetypes, we can "manually" tell a generic icon. if ($generic_name = _filefield_generic_icon_map($file)) { if ($iconpath = _filefield_create_icon_path($generic_name, $theme)) { return $iconpath; } } // Use generic icons for each category that provides such icons. foreach (array('audio', 'image', 'text', 'video') as $category) { if (strpos($file['filemime'], $category .'/') === 0) { if ($iconpath = _filefield_create_icon_path($category .'-x-generic', $theme)) { return $iconpath; } } } // Try application-octet-stream as last fallback. if ($iconpath = _filefield_create_icon_path('application-octet-stream', $theme)) { return $iconpath; } // Sorry, no icon can be found... return FALSE; } function _filefield_create_icon_path($iconname, $theme = 'protocons') { $iconpath = drupal_get_path('module', 'filefield') .'/icons/'. $theme .'/16x16/mimetypes/'. $iconname .'.png'; if (file_exists($iconpath)) { return $iconpath; } return FALSE; } function _filefield_generic_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; } }