You are here

filefield_sources.module in FileField Sources 6

Same filename and directory in other branches
  1. 8 filefield_sources.module
  2. 7 filefield_sources.module

Extend FileField to allow files from multiple sources.

File

filefield_sources.module
View source
<?php

/**
 * @file
 * Extend FileField to allow files from multiple sources.
 */

/**
 * Implements hook_menu().
 */
function filefield_sources_menu() {
  $params = array();
  return filefield_sources_invoke_all('menu', $params);
}

/**
 * Implements hook_elements().
 */
function filefield_sources_elements() {
  $elements = array();
  foreach (module_invoke_all('filefield_sources_widgets') as $widget) {
    $elements[$widget]['#process'] = array(
      'filefield_sources_field_process',
    );
    $elements[$widget]['#pre_render'] = array(
      'filefield_sources_field_pre_render',
    );
    $elements[$widget]['#element_validate'] = array(
      'filefield_sources_field_validate',
    );
    $elements[$widget]['#filefield_value_callback'] = array(
      'filefield_sources_field_value',
    );
  }
  return $elements;
}

/**
 * Implements hook_theme().
 */
function filefield_sources_theme() {
  $params = array();
  $theme = filefield_sources_invoke_all('theme', $params);
  $theme['filefield_sources_list'] = array(
    'arguments' => array(
      'sources' => NULL,
    ),
  );
  return $theme;
}

/**
 * Implements hook_filefield_sources_widgets().
 *
 * This returns a list of widgets that are compatible with FileField Sources.
 */
function filefield_sources_filefield_sources_widgets() {
  return array(
    'filefield_widget',
    'imagefield_widget',
  );
}

/**
 * Implements hook_widget_settings_alter().
 */
function filefield_sources_widget_settings_alter(&$settings, $op, $widget) {

  // Only support modules that implement hook_insert_widgets().
  $widget_type = isset($widget['widget_type']) ? $widget['widget_type'] : $widget['type'];
  if (!in_array($widget_type, module_invoke_all('filefield_sources_widgets'))) {
    return;
  }
  if ($op == 'form') {
    $settings = array_merge($settings, filefield_sources_form($widget));
  }
  if ($op == 'save') {
    $settings = array_merge($settings, filefield_sources_widget_settings($widget));
  }
}

/**
 * A list of settings needed by FileField Sources module on widgets.
 */
function filefield_sources_widget_settings($widget) {
  $settings = array(
    'filefield_sources',
  );
  $params = array(
    'save',
    $widget,
  );
  $settings = array_merge($settings, filefield_sources_invoke_all('settings', $params));
  return $settings;
}

/**
 * Configuration form for editing FileField Sources settings for a widget.
 */
function filefield_sources_form($widget) {
  $settings = $widget;
  $form['filefield_sources'] = array(
    '#type' => 'fieldset',
    '#title' => t('File sources'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => 15,
  );
  $sources = filefield_sources_list(FALSE);
  $sources = isset($settings['filefield_sources']) ? array_intersect_key(array_merge($settings['filefield_sources'], $sources), $sources) : $sources;
  $form['filefield_sources']['filefield_sources'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Enabled sources'),
    '#options' => $sources,
    '#default_value' => isset($settings['filefield_sources']) ? $settings['filefield_sources'] : array(),
    '#description' => t('Select the available locations from which this widget may select files.'),
  );
  $params = array(
    'form',
    $settings,
  );
  $form['filefield_sources'] = array_merge($form['filefield_sources'], filefield_sources_invoke_all('settings', $params));
  return $form;
}

/**
 * A #process callback to extend the filefield_widget element type.
 *
 * Add the central JavaScript and CSS files that allow switching between
 * different sources. Third-party modules can also add to the list of sources
 * by implementing hook_filefield_sources_info().
 */
function filefield_sources_field_process($element, $edit, &$form_state, $form) {
  static $js_added;

  // If not a recognized field instance, do not process.
  if (!isset($element['#field_name']) || !($field = content_fields($element['#field_name'], $element['#type_name']))) {
    return $element;
  }

  // Do all processing as needed by each source.
  $sources = filefield_sources_info();
  $enabled_sources = isset($field['widget']['filefield_sources']) ? $field['widget']['filefield_sources'] : array();
  foreach ($sources as $source_name => $source) {
    if (empty($enabled_sources[$source_name])) {
      unset($sources[$source_name]);
    }
    elseif (isset($source['process'])) {
      $function = $source['process'];
      $element = $function($element, $edit, $form_state, $form);
    }
  }

  // Exit out if not adding any sources.
  if (empty($sources)) {
    return $element;
  }

  // Add basic JS and CSS.
  $path = drupal_get_path('module', 'filefield_sources');
  drupal_add_css($path . '/filefield_sources.css');
  drupal_add_js($path . '/filefield_sources.js');

  // Check the element for hint text that might need to be added.
  foreach (element_children($element) as $key) {
    if (isset($element[$key]['#filefield_sources_hint_text']) && !isset($js_added[$key])) {
      $type = str_replace('filefield_', '', $key);
      drupal_add_js(array(
        'fileFieldSources' => array(
          $type => array(
            'hintText' => $element[$key]['#filefield_sources_hint_text'],
          ),
        ),
      ), 'setting');
      $js_added[$key] = TRUE;
    }
  }

  // Add the list of sources to the element for toggling between sources.
  if (empty($element['fid']['#value'])) {
    $element['filefield_sources_list'] = array(
      '#type' => 'markup',
      '#value' => theme('filefield_sources_list', $element, $sources),
      '#weight' => -20,
    );
  }
  return $element;
}

/**
 * A #pre_render function to hide sources if a file is currently uploaded.
 */
function filefield_sources_field_pre_render($element) {

  // If we already have a file, we don't want to show the upload controls.
  if (!empty($element['fid']['#value'])) {
    foreach (element_children($element) as $key) {
      if (!empty($element[$key]['#filefield_source'])) {
        $element[$key]['#access'] = FALSE;
      }
    }
  }
  return $element;
}

/**
 * An #element_validate function to run source validations.
 */
function filefield_sources_field_validate($element, &$form_state, $form) {

  // Do all processing as needed by each source.
  $sources = filefield_sources_info();
  foreach ($sources as $source) {
    if (isset($source['validate'])) {
      $function = $source['validate'];
      $function($element, $form_state, $form);
    }
  }
}

/**
 * A #filefield_value_callback to run source value callbacks.
 */
function filefield_sources_field_value($element, &$item) {

  // Do all processing as needed by each source.
  $sources = filefield_sources_info();
  foreach ($sources as $source) {
    if (isset($source['value'])) {
      $function = $source['value'];
      $function($element, $item);
    }
  }
}

/**
 * Call all FileField Source hooks stored in the available include files.
 */
function filefield_sources_invoke_all($method, &$params) {
  $return = array();
  foreach (filefield_sources_includes() as $source) {
    $function = 'filefield_source_' . $source . '_' . $method;
    if (function_exists($function)) {
      $result = call_user_func_array($function, $params);
      if (isset($result) && is_array($result)) {
        $return = array_merge_recursive($return, $result);
      }
      elseif (isset($result)) {
        $return[] = $result;
      }
    }
  }
  return $return;
}

/**
 * Load hook_filefield_sources_info() data from all modules.
 */
function filefield_sources_info() {
  $info = module_invoke_all('filefield_sources_info');
  drupal_alter('filefield_sources_info', $info);
  uasort($info, '_filefield_sources_sort');
  return $info;
}

/**
 * Create a list of FileField Sources by name, suitable for a select list.
 */
function filefield_sources_list($include_default = TRUE) {
  $info = filefield_sources_info();
  $list = array();
  if ($include_default) {
    $list['upload'] = t('Upload');
  }
  foreach ($info as $key => $source) {
    $list[$key] = $source['name'];
  }
  return $list;
}

/**
 * Implements hook_filefield_sources_info().
 */
function filefield_sources_filefield_sources_info() {
  $params = array();
  return filefield_sources_invoke_all('info', $params);
}

/**
 * Load all the potential sources.
 */
function filefield_sources_includes($include = TRUE, $enabled_only = TRUE) {
  if ($enabled_only) {
    $enabled_includes = variable_get('filefield_sources', filefield_sources_includes(FALSE, FALSE));
  }
  $includes = array();
  $directory = drupal_get_path('module', 'filefield_sources') . '/sources';
  foreach (file_scan_directory($directory, '.inc$') as $file) {
    if (!$enabled_only || in_array($file->name, $enabled_includes)) {
      $includes[] = $file->name;
      if ($include) {
        include_once $file->filename;
      }
    }
  }
  return $includes;
}

/**
 * Check the current user's access to a file through hook_file_download().
 *
 * @param $uri
 *   A file URI as loaded from the database.
 * @return
 *   Boolean TRUE if the user has access, FALSE otherwise.
 *
 * @see file_download()
 * @see hook_file_download().
 */
function filefield_sources_file_access($filepath) {
  $headers = array();
  foreach (module_implements('file_download') as $module) {
    $function = $module . '_file_download';
    $result = $function($filepath);
    if ($result == -1) {

      // Throw away the headers received so far.
      $headers = array();
      break;
    }
    if (isset($result) && is_array($result)) {
      $headers = array_merge($headers, $result);
    }
  }
  return !empty($headers);
}

/**
 * Clean up the file name, munging extensions and transliterating.
 *
 * @param $filepath
 *   A string containing a file name or full path. Only the file name will
 *   actually be modified.
 * @return
 *   A file path with a cleaned-up file name.
 */
function filefield_sources_clean_filename($filepath) {
  global $user;
  $filename = basename($filepath);
  if (module_exists('transliteration')) {
    module_load_include('inc', 'transliteration');
    $langcode = NULL;
    if (!empty($_POST['language'])) {
      $languages = language_list();
      $langcode = isset($languages[$_POST['language']]) ? $_POST['language'] : NULL;
    }
    $filename = transliteration_clean_filename($filename, $langcode);
  }

  // Because this transfer mechanism does not use file_save_upload(), we need
  // to manually munge the filename to prevent dangerous extensions.
  // See file_save_upload().
  $extensions = '';
  foreach ($user->roles as $rid => $name) {
    $extensions .= ' ' . variable_get("upload_extensions_{$rid}", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
  }
  $filename = file_munge_filename($filename, $extensions);
  $directory = dirname($filepath);
  return ($directory ? $directory . '/' : $directory) . $filename;
}

/**
 * Theme the display of the sources list.
 */
function theme_filefield_sources_list($element, $sources) {
  $links = array();

  // Add the default "Upload" since it's not in our list.
  $default['upload'] = array(
    'label' => t('Upload'),
    'description' => t('Upload a file from your computer.'),
  );
  $sources = array_merge($default, $sources);
  foreach ($sources as $name => $source) {
    $links[] = '<a href="#" onclick="return false;" title="' . $source['description'] . '" id="' . $element['#id'] . '-' . $name . '-source" class="filefield-source filefield-source-' . $name . '">' . $source['label'] . '</a>';
  }
  return '<div class="filefield-sources-list">' . implode(' | ', $links) . '</div>';
}

/**
 * Validate a file based on the $element['#upload_validators'] property.
 */
function filefield_sources_element_validate($element, $file) {
  $validators = $element['#upload_validators'];
  $errors = array();

  // Since this frequently is used to reference existing files, check that
  // they exist first in addition to the normal validations.
  if (!file_exists($file->filepath)) {
    $errors[] = t('The file does not exist.');
  }
  else {
    foreach ($validators as $function => $args) {

      // Add the $file variable to the list of arguments and pass it by
      // reference (required for PHP 5.3 and higher).
      array_unshift($args, NULL);
      $args[0] =& $file;
      $errors = array_merge($errors, call_user_func_array($function, $args));
    }
  }

  // Check for validation errors.
  if (!empty($errors)) {
    $message = t('The selected file %name could not be referenced.', array(
      '%name' => $file->filename,
    ));
    if (count($errors) > 1) {
      $message .= '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
    }
    else {
      $message .= ' ' . array_pop($errors);
    }
    form_error($element, $message);
    return 0;
  }
  return 1;
}

/**
 * Generate help text based on the $element['#upload_validators'] property.
 */
function filefield_sources_element_validation_help($validators) {
  $desc = array();
  foreach ($validators as $callback => $arguments) {
    $help_func = $callback . '_help';
    if (function_exists($help_func)) {
      $desc[] = call_user_func_array($help_func, $arguments);
    }
  }
  return empty($desc) ? '' : implode('<br />', $desc);
}

/**
 * Custom sort function for ordering sources.
 */
function _filefield_sources_sort($a, $b) {
  $a = (array) $a + array(
    'weight' => 0,
    'label' => '',
  );
  $b = (array) $b + array(
    'weight' => 0,
    'label' => '',
  );
  return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : strnatcasecmp($a['label'], $b['label']));
}

Functions

Namesort descending Description
filefield_sources_clean_filename Clean up the file name, munging extensions and transliterating.
filefield_sources_elements Implements hook_elements().
filefield_sources_element_validate Validate a file based on the $element['#upload_validators'] property.
filefield_sources_element_validation_help Generate help text based on the $element['#upload_validators'] property.
filefield_sources_field_pre_render A #pre_render function to hide sources if a file is currently uploaded.
filefield_sources_field_process A #process callback to extend the filefield_widget element type.
filefield_sources_field_validate An #element_validate function to run source validations.
filefield_sources_field_value A #filefield_value_callback to run source value callbacks.
filefield_sources_filefield_sources_info Implements hook_filefield_sources_info().
filefield_sources_filefield_sources_widgets Implements hook_filefield_sources_widgets().
filefield_sources_file_access Check the current user's access to a file through hook_file_download().
filefield_sources_form Configuration form for editing FileField Sources settings for a widget.
filefield_sources_includes Load all the potential sources.
filefield_sources_info Load hook_filefield_sources_info() data from all modules.
filefield_sources_invoke_all Call all FileField Source hooks stored in the available include files.
filefield_sources_list Create a list of FileField Sources by name, suitable for a select list.
filefield_sources_menu Implements hook_menu().
filefield_sources_theme Implements hook_theme().
filefield_sources_widget_settings A list of settings needed by FileField Sources module on widgets.
filefield_sources_widget_settings_alter Implements hook_widget_settings_alter().
theme_filefield_sources_list Theme the display of the sources list.
_filefield_sources_sort Custom sort function for ordering sources.