'filefield_js', 'page arguments' => array(3, 4, 5, 'filefield_file_upload_js'), 'access callback' => 'filefield_edit_access', 'access arguments' => array(3), 'type' => MENU_CALLBACK, 'file' => 'filefield.widget.inc', ); $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, 'file' => 'filefield.widget.inc', ); 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_draggable_settings_table' => array( 'arguments' => array('element' => NULL), 'file' => 'filefield.theme.inc', ), 'filefield_container_item' => array( 'arguments' => array('element' => NULL), 'file' => 'filefield.theme.inc', ), 'filefield_icon' => array( 'arguments' => array('file' => NULL), 'file' => 'filefield.theme.inc', ), 'filefield_file_upload' => array( 'arguments' => array('element' => NULL), 'file' => 'filefield.widget.inc', ), 'filefield_file_edit' => array( 'arguments' => array('element' => NULL), 'file' => 'filefield.widget.inc', ), 'filefield_generic_edit' => array( 'arguments' => array('element' => NULL), //'file' => this one, ), 'filefield_formatter_default' => array( 'arguments' => array('element' => NULL), 'file' => 'filefield.formatter.inc', ), 'filefield_unguarded' => array( 'arguments' => array('file' => NULL, 'field' => NULL), 'file' => 'filefield.formatter.inc', ), 'filefield' => array( 'arguments' => array('file' => NULL, 'field' => NULL), 'file' => 'filefield.formatter.inc', ), 'filefield_file_formatter_generic' => array( 'arguments' => array( 'file' => NULL, 'field' => NULL, 'file_formatter_settings' => NULL, ), 'file' => 'filefield.formatter.inc', ), ); } /** * 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) { include_once(drupal_get_path('module', 'filefield') .'/filefield.theme.inc'); return _filefield_icon_url($file); } /** * Implementation of hook_file(). * (Which is implemented by filefield/imagefield in Drupal 6 yet). */ function filefield_file($op, $file) { switch ($op) { // report references to prevent deletion of files associated to older revisions // or by other fields. case 'references': $references = 0; foreach(content_fields() as $field) { if ($field['type'] != 'file') { continue; } $db_info = content_database_info($field); $references += db_result(db_query( 'SELECT count('. $db_info['columns']['fid']['column'] .') FROM {'. $db_info['table'] .'} WHERE '. $db_info['columns']['fid']['column'] .' = %d', $file->fid )); if (isset($file->filefield_delete) && $field['field_name'] == $file->filefield_delete) { --$references; // doesn't count as it's being deleted } } return array('filefield' => $references); } } /** * 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.'), ); $form['file_formatters'] = array( '#title' => t('File display'), '#description' => t('Control how files may be displayed in the node view and other views for this field. If no formatters are enabled or are able to handle a file then that specific file will not be displayed. You can also reorder the formatters to specify their priority: the top-most enabled formatter always gets to display the files that it supports, whereas the bottom-most enabled formatter only gets to handle them if the file is not supported by any other other one.'), '#tree' => TRUE, '#weight' => 5, '#theme' => 'filefield_draggable_settings_table', '#settings_type' => 'formatters', // info for the theme function ); $file_formatter_info = _filefield_file_formatter_info($field); // Present the formatters in the order that was determined above. $weight = 1; foreach ($file_formatter_info as $file_formatter => $info) { $form['file_formatters'][$file_formatter]['enabled'] = array( '#type' => 'checkbox', '#title' => $info['title'], '#description' => $info['description'], '#default_value' => $info['enabled'], ); $form['file_formatters'][$file_formatter]['weight'] = array( '#type' => 'weight', '#delta' => count($file_formatter_info), '#default_value' => $weight, ); // Let modules add their own formatter specific settings. $file_formatter_settings = isset($field['file_formatters'][$file_formatter]) ? $field['file_formatters'][$file_formatter] : array(); $additions = module_invoke( $info['module'], 'file_formatter_settings_'. $info['name'], 'form', $file_formatter_settings ); if (is_array($additions)) { $form['file_formatters'][$file_formatter] = array_merge( $form['file_formatters'][$file_formatter], $additions ); } ++$weight; } return $form; case 'validate': // Let modules add their own formatter specific validations. $file_formatter_info = _filefield_file_formatter_info($widget); foreach ($file_formatter_info as $file_formatter => $info) { $file_formatter_settings = isset($field['file_formatters'][$file_formatter]) ? $field['file_formatters'][$file_formatter] : array(); module_invoke( $info['module'], 'file_formatter_settings_'. $info['name'], 'validate', $file_formatter_settings ); } break; case 'save': return array('force_list', 'file_formatters'); 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 CCK's 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 CCK's 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) { // For hook_file($op = 'references'), remember that this is being deleted. $item['filefield_delete'] = $field['field_name']; if (field_file_delete($item)) { unset($items[$delta]); } } $items = array_values($items); // compact deltas break; case 'delete': foreach ($items as $delta => $item) { // For hook_file($op = 'references'), remember that this is being deleted. $item['filefield_delete'] = $field['field_name']; 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 CCK's 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, ), ); } /** * Implementation of CCK's 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 CCK's 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'); } $form['file_widgets'] = array( '#title' => t('File widgets'), '#description' => t('Control which kinds of files may be uploaded to the edit form for this field, by specifying the widgets that can handle the desired file types. You can also reorder the widgets to specify their priority: the top-most enabled widget always gets to handle the files that it supports, whereas the bottom-most enabled widget only gets to handle them if the file is not supported by any other other one.'), '#required' => TRUE, '#tree' => TRUE, '#weight' => 5, '#theme' => 'filefield_draggable_settings_table', '#settings_type' => 'widgets', // info for the theme function ); $file_widget_info = _filefield_file_widget_info($widget); // Present the widgets in the order that was determined above. $weight = 1; foreach ($file_widget_info as $file_widget_name => $info) { $form['file_widgets'][$file_widget_name]['enabled'] = array( '#type' => 'checkbox', '#title' => $info['title'], '#description' => $info['description'], '#default_value' => $info['enabled'], ); $form['file_widgets'][$file_widget_name]['weight'] = array( '#type' => 'weight', '#delta' => count($file_widget_info), '#default_value' => $weight, ); // Let modules add their own widget specific settings. $file_widget_settings = isset($widget['file_widgets'][$file_widget_name]) ? $widget['file_widgets'][$file_widget_name] : array(); $additions = module_invoke( $info['module'], 'file_widget_settings_'. $info['name'], 'form', $file_widget_settings ); if (is_array($additions)) { $form['file_widgets'][$file_widget_name] = array_merge( $form['file_widgets'][$file_widget_name], $additions ); } ++$weight; } return $form; case 'validate': $valid = FALSE; foreach ($widget['file_widgets'] as $file_widget_name => $info) { if ($info['enabled']) { $valid = TRUE; break; } } if (!$valid) { form_set_error('file_widgets', t('At least one type of file widgets must be enabled.')); } // Let modules add their own widget specific validations. $file_widget_info = _filefield_file_widget_info($widget); foreach ($file_widget_info as $file_widget_name => $info) { $file_widget_settings = isset($widget['file_widgets'][$file_widget_name]) ? $widget['file_widgets'][$file_widget_name] : array(); module_invoke( $info['module'], 'file_widget_settings_'. $info['name'], 'validate', $file_widget_settings ); } break; case 'save': return array('file_extensions', 'file_path', 'file_widgets'); } } 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'], '\\/'); } /** * Add all CSS files that extension file widgets might need, so that they * exist even if the form element has only been inserted by JavaScript. * Can also be used for formatter info arrays as those look similar. */ function _filefield_add_css($widget_or_formatter_info) { static $done = FALSE; if ($done) { return; // adding those files once should be enough for each request } foreach ($widget_or_formatter_info as $name => $info) { foreach ($info['css'] as $path) { drupal_add_css($path); } } } /** * Determine which widget will be used for displaying the edit form * for the given file. * * @return * An array with info about the most appropriate file widget, * or NULL if no widget is available to edit this file. */ function filefield_widget_for_file($file, $field, $field_widget) { $file_widget_info = _filefield_file_widget_info($field_widget); $file = (object) $file; // other modules only get to see objects $suitability_args = array($file, $field, $field_widget); return _filefield_extension_for_file($file_widget_info, $suitability_args); } /** * Determine which formatter will be used for displaying the edit form * for the given file. * * @return * An array with info about the most appropriate file formatter, * or NULL if no formatter is available to display this file. */ function filefield_formatter_for_file($file, $field) { $file_formatter_info = _filefield_file_formatter_info($field); $file = (object) $file; // other modules only get to see objects $suitability_args = array($file, $field); return _filefield_extension_for_file($file_formatter_info, $suitability_args); } /** * Common implementation of filefield_widget_for_file() and * filefield_formatter_for_file(). */ function _filefield_extension_for_file($file_extension_info, $suitability_args) { $suitable_extension_info = array(); foreach ($file_extension_info as $extension_name => $info) { if (!$info['enabled']) { continue; // the admin disabled this widget or formatter } $handles_file = $info['suitability callback']; // Either $handles_file is TRUE already or it's a function that // will return TRUE if the widget/formatter handles this file. if (is_string($handles_file)) { $handles_file = call_user_func_array($handles_file, $suitability_args); } if ($handles_file !== TRUE) { continue; // this widget/formatter is not interested in our file } $suitable_extension_info[] = $info; } // Return the most appropriate widget/formatter, if one was found. return empty($suitable_extension_info) ? NULL : reset($suitable_extension_info); } /** * Retrieve information about the widgets that are going to preview and * edit the single files that are uploaded in CCK's edit form. * This function also sorts the widgets in the way that the administrator * has specified for this field. */ function _filefield_file_widget_info($field_widget) { $file_widget_info = _filefield_file_extension_info_original('widget'); return _filefield_file_extension_info($file_widget_info, $field_widget['file_widgets']); } /** * Retrieve information about the formatters that are going to display the * single files for the node view or other views. * This function also sorts the formatters in the way that the administrator * has specified for this field. */ function _filefield_file_formatter_info($field) { $file_formatter_info = _filefield_file_extension_info_original('formatter'); return _filefield_file_extension_info($file_formatter_info, $field['file_formatters']); } /** * Common implementation for _filefield_file_widget_info() and * _filefield_file_formatter_info(): sort and the given extensions and mark * them as enabled or not, based on the extension $settings from * hook_widget_settings() or hook_field_settings() respectively. */ function _filefield_file_extension_info($file_extension_info, $settings) { // Sort and enable the formatters according to previous admin settings or defaults. foreach ($file_extension_info as $extension_name => $info) { if (isset($settings[$extension_name]['weight'])) { $info['weight'] = $settings[$extension_name]['weight']; } else { // By default, the generic file widget/formatter should be last in the // list of possible formatters, and other new formatters should also not // be preferred to ones that already had their weight configured before. $info['weight'] = ($extension_name == 'filefield_generic') ? 1000 : 999; } if (isset($settings[$extension_name]['enabled'])) { $info['enabled'] = $settings[$extension_name]['enabled']; } else { // By default, enable only the generic file widget/formatter, so that // newly enabled modules don't show their widges/formatters without // approval of the admin. $info['enabled'] = ($extension_name == 'filefield_generic'); } $file_extension_info[$extension_name] = $info; } return _filefield_sort_by_weight($file_extension_info); } function _filefield_file_extension_info_original($extension_type) { static $file_extension_info = array(); // with 'widget' and 'formatter' keys if (!isset($file_extension_info[$extension_type])) { $file_extension_info[$extension_type] = array(); $hook = 'file_'. $extension_type .'_info'; foreach (module_implements($hook) as $module) { $function = $module .'_'. $hook; $module_extension_info = $function(); // Prepare the array for mass consumption. foreach ($module_extension_info as $extension_name => $info) { if ($extension_type == 'formatter') { $info['theme'] = $module .'_file_formatter_'. $extension_name; } $info['module'] = $module; $info['name'] = $extension_name; $info['css'] = isset($info['css']) ? $info['css'] : array(); $info['key'] = $module .'_'. $extension_name; $file_extension_info[$extension_type][$info['key']] = $info; } } drupal_alter($hook, $file_extension_info[$extension_type]); } return $file_extension_info[$extension_type]; } /** * Helper function to sort file formatter/widget settings according to * user drag-n-drop reordering. */ function _filefield_sort_by_weight($items) { uasort($items, '_filefield_sort_by_weight_helper'); foreach ($items as $delta => $item) { unset($items[$delta]['weight']); } return $items; } /** * Sort function for file formatter/widget order. * (copied form element_sort(), which acts on #weight keys) */ function _filefield_sort_by_weight_helper($a, $b) { $a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0; $b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0; if ($a_weight == $b_weight) { return 0; } return ($a_weight < $b_weight) ? -1 : 1; } /** * Implementation of filefield's hook_file_formatter_info(). */ function filefield_file_formatter_info() { return array( 'generic' => array( 'suitability callback' => TRUE, 'title' => t('Generic files'), 'description' => t('Displays all kinds of files with an icon and a linked file description.'), ), ); } /** * Implementation of filefield's hook_file_widget_info(). */ function filefield_file_widget_info() { return array( 'generic' => array( 'form element' => 'filefield_generic_edit', 'suitability callback' => TRUE, 'title' => t('Generic files'), 'description' => t('An edit widget for all kinds of files.'), ), ); } /** * 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['#file']; $url = file_create_url($file->filepath); $prefix = isset($element['#prefix']) ? $element['#prefix'] : ''; $suffix = isset($element['#suffix']) ? $element['#suffix'] : ''; $element['#prefix'] = $prefix .'