<?php
// $Id: file_cck.module,v 1.25 2009/08/20 13:48:45 miglius Exp $

/**
 * @file
 * Integrates file operations with the CCK module.
 */

//////////////////////////////////////////////////////////////////////////////

define('FILE_CCK_VOCABULARIES_ALL', variable_get('file_cck_vocabularies_all', 1));
define('FILE_CCK_VOCABULARIES',     serialize(variable_get('file_cck_vocabularies', array())));
define('FILE_CCK_OG_INHERITANCE',   variable_get('file_cck_og_inheritance', 0));
define('FILE_CCK_OG_VOCABULARIES',  variable_get('file_cck_og_vocabularies', 0));

//////////////////////////////////////////////////////////////////////////////
// Core API hooks

/**
 * Implementation of hook_theme().
 */
function file_cck_theme() {
  return array(
    'file_cck_file' => array(
      'arguments' => array('element' => NULL),
    ),
    'file_cck_formatter_default' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_file' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_preview' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_preview_file' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_preview_formats' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_preview_formats_file' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_thumbnail' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_thumbnail_open' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_formatter_thumbnail_download' => array('arguments' => array('element' => NULL), 'function' => 'theme_file_cck_formatter_generic'),
    'file_cck_field_remove' => array(
      'arguments' => array('form' => NULL),
    ),
  );
}

function theme_file_cck_field_remove(&$form) {
  $id = $form['value']['#parents'][1];
  $form[$id] = array(
    '#id' => $form['#field_name'] .'_remove_'. $id,
    '#name' => $form['#field_name'] .'_remove['. $id .']',
    '#type' => 'checkbox',
    '#title' => t('Remove'),
    '#value' => ($field = $form['#field_name'] .'_remove') && isset($_POST[$field])  ? (isset($_POST[$field][$id]) ? 1 : 0) : $form['#is_checked'],
  );
  return drupal_render($form);
}

/**
 * Implementation of hook_menu().
 */
function file_cck_menu() {
  return array(
    'admin/settings/file/cck' => array(
      'title' => 'CCK',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('file_cck_admin_settings'),
      'access arguments' => array('administer site configuration'),
      'file' => 'file_cck.admin.inc',
    ),
    'file_cck/js' => array(
      'page callback' => 'file_cck_js',
      'access arguments' => array('access content'),
      'type' => MENU_CALLBACK,
    ),
  );
}

/**
 * Implementation of hook_nodeapi().
 */
function file_cck_nodeapi(&$node, $op, $a3, $a4) {
  switch ($op) {
    case 'prepare':
      // The session variable is cleared from the unsaved file uploads.
      unset($_SESSION['file_cck_files']);
      break;
    case 'insert':
    case 'update':
      if ($node->type != 'file') {
        // Executing sqls to update cck fields.
        _file_cck_db_update();
        unset($_SESSION['file_cck_files']);
      }
      break;
    case 'view':
      $type = content_types($node->type);
      foreach ($type['fields'] as $field => $data) {
        if ($data['type'] == 'file_cck' && is_array($node->$field)) {
          $db_info = content_database_info($data);
          $order = isset($_POST[$field]) ? array_keys($_POST[$field]) : array_keys($node->$field);
          $multiple = count($order) > 1;
          $delta = 0;
          foreach ($order as $o) {
            unset($fid);
            if (isset($_SESSION['file_cck_files']) && isset($_SESSION['file_cck_files']['s_'. $field .'_'. $o]) && ($file = $_SESSION['file_cck_files']['s_'. $field .'_'. $o]) && is_object($file)) {
              $fid = 's_'. $field .'_'. $o;
            }
            else if ($nid = db_result(db_query('SELECT '. $db_info['columns']['value']['column'] .' FROM {'. $db_info['table'] .'} WHERE vid = %d'. ($multiple ? ' AND delta = '. $o : ''), $node->vid))) {
              $fid = $nid;
            }
            if (isset($fid) && (!isset($_POST[$field .'_remove']) || !isset($_POST[$field .'_remove'][$o]))) {
              // The items array is populated just for the first item, therefore I copy it
              // and populate with the right value and the weight.
              if ($delta > 0)
                $node->content[$field]['field']['items'][$delta] = $node->content[$field]['field']['items'][0];
              $node->content[$field]['field']['items'][$delta]['#item']['value'] = $fid;
              $node->content[$field]['field']['items'][$delta]['#weight'] = $delta;
            }
            $delta++;
          }
        }
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function file_cck_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    // A node form.
    // If a file is already uploaded, the corresponding message
    // is set next to the file upload field.
    if ($form['type']['#value'] .'_node_form' == $form_id) {
      $node = $form['#node'];
      $type = content_types($node->type);
      foreach ($type['fields'] as $field => $data) {
        if ($data['type'] == 'file_cck') {
          $form['#attributes']['enctype'] = 'multipart/form-data';
          $db_info = content_database_info($data);
          foreach ($form[$field] as $delta => $field_data) {
            if (!is_array($field_data))
              continue;

            if ($delta === $field .'_add_more') {
              $form[$field][$delta]['#submit'] = array('node_form_submit_build_node');
              $form[$field][$delta]['#ahah'] = array('path' => 'file_cck/js', 'wrapper' => strtr($field, '_', '-')  .'-add-more-wrapper', 'progress' => array('type' => 'bar', 'message' => t('Please wait...')));
              $form[$field][$delta]['#suffix'] .= '<script type=\'text/javascript\'>$(document).ready(function() { $("#edit-'. strtr($field, '_', '-') .'-'. strtr($field, '_', '-') .'-add-more").mousedown(function() { $("#edit-file-cck-current-field").val("'. $field .'"); }); });</script>';
              continue;
            }

            $title = NULL;
            if (isset($_SESSION['file_cck_files']) && isset($_SESSION['file_cck_files']['s_'. $field .'_'. $delta]) && ($file = $_SESSION['file_cck_files']['s_'. $field .'_'. $delta]) && is_object($file)) {
              $title = $file->name;
            }
            else if (isset($node->vid) && ($nid = db_result(db_query('SELECT '. $db_info['columns']['value']['column'] .' FROM {'. $db_info['table'] .'} WHERE vid = %d'. (!empty($data['multiple']) ? ' AND delta = '. $delta : ''), $node->vid)))) {
              $node_file = node_load($nid);
              $title = $node_file->title;
            }
            if (isset($title))
              $form[$field][$delta]['#prefix'] = t('A file %file has already been uploaded. If you upload another file the current file data will be replaced.', array('%file' => $title));
            $form[$field][$delta]['#theme'] = 'file_cck_field_remove';
            $form[$field][$delta]['#is_checked'] = isset($form_state['clicked_button']) && isset($form_state['clicked_button']['#post']) && isset($form_state['clicked_button']['#post'][$field .'_remove']) && isset($form_state['clicked_button']['#post'][$field .'_remove'][$delta]) ? 1 : 0;

            // Reseting #required flag, since it does not allow uploading files.
            // Will check if the file is uploaded in the validate hook.
            $form[$field][$delta]['#required'] = '';
          }
        }
      }
      $form['file_cck_current_field'] = array('#type' => 'hidden', '#value' => '');
    }
  }
}

//////////////////////////////////////////////////////////////////////////////
// CCK API hooks

/**
 * Implementation of hook_field_info().
 */
function file_cck_field_info() {
  return array(
    'file_cck' => array(
      'label' => t('File'),
      'description' => t('Store file in the system. A file node will be created.'),
      'callbacks' => array(
        'tables' => CONTENT_CALLBACK_DEFAULT,
        'arguments' => CONTENT_CALLBACK_DEFAULT,
        ),
      ),
    );
}

/**
 * Implementation of hook_field_settings().
 */
function file_cck_field_settings($op, $field) {
  switch ($op) {
    case 'database columns':
      if ($field['type'] == 'file_cck') {
        return array(
          'value' => array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE),
        );
      }
      break;
    case 'views data':
      $data = content_views_field_views_data($field);
      return $data;
  }
}

/**
 * Implementation of hook_field().
 */
function file_cck_field($op, &$node, $field, &$items, $teaser, $page) {
  switch ($op) {
    case 'validate':
      $db_info = content_database_info($field);
      $order = isset($_POST[$field['field_name']]) ? array_keys($_POST[$field['field_name']]) : array(0);
      $multiple = count($order) > 1;
      $delta = 0;
      foreach ($order as $o) {
        if ($upload = file_save_upload($field['field_name'] .'_'. $o, array_merge(file_get_validators(), array('file_cck_upload_validate' => array(isset($field['widget']['extensions_allowed']) ? $field['widget']['extensions_allowed'] : ''))))) {
          $node_file = (object)array('nosave' => TRUE);
          if (!file_node_save($node_file, $upload)) {
            drupal_set_message(t("Error saving file %file. Please, contact site administrator.", array('%file' => $upload->filename)), 'error');
          }
          else {
            $fid = 's_'. $field['field_name'] .'_'. $o;
            $node_file->file->sid = $fid;
            $_SESSION['file_cck_files'][$fid] = $node_file->file;
          }
        }
        else if ($delta == 0 && $field['required'] == 1 && (!isset($_SESSION['file_cck_files']) || !isset($_SESSION['file_cck_files']['s_'. $field['field_name'] .'_'. $delta]))) {
          if (isset($_POST[$field['field_name'] .'_remove'][$o]) || !db_result(db_query('SELECT '. $db_info['columns']['value']['column'] .' FROM {'. $db_info['table'] .'} WHERE vid = %d'. ($multiple ? ' AND delta = '. $o : ''), $node->vid))) {
            form_set_error('files]['. $field['field_name'] .'_'. $delta, t('%title field is required.', array('%title' => $field['widget']['label'])), 'error');
          }
        }
        $delta++;
      }
      break;
    case 'insert':
    case 'update':
      $db_info = content_database_info($field);
      $order = isset($_POST[$field['field_name']]) ? array_keys($_POST[$field['field_name']]) : array(0);
      $multiple = count($order) > 1;
      $delta = 0;

      foreach ($order as $o) {
        // I cannot modify field entry here since cck module resets changes made to the fields.
        // Therefore I create updates queue which is executed from the hook_nodeapi().

        $nid = NULL;
        if (isset($_POST[$field['field_name'] .'_remove'][$o])) {
          _file_cck_db_update(array('action' => $field['multiple'] != 1 ? 'remove' : 'delete', 'table' => $db_info['table'], 'column' => $db_info['columns']['value']['column'], 'multiple' => $multiple, 'delta' => $delta, 'vid' => $node->vid));
        }
        else if (($file = file_save_upload($field['field_name'] .'_'. $o, array_merge(file_get_validators(), array('file_cck_upload_validate' => array(isset($field['widget']['extensions_allowed']) ? $field['widget']['extensions_allowed'] : ''))))) || (isset($_SESSION['file_cck_files']) && isset($_SESSION['file_cck_files']['s_'. $field['field_name'] .'_'. $o]) && $file = $_SESSION['file_cck_files']['s_'. $field['field_name'] .'_'. $o])) {
          $file_node = (object)array('file' => $file);
          if (FILE_CCK_OG_INHERITANCE)
            _file_propagate_og($node, $file_node);
          _file_propagate_terms($node, $file_node, array('all' => FILE_CCK_VOCABULARIES_ALL, 'og' => FILE_CCK_OG_VOCABULARIES, 'vocabs' => unserialize(FILE_CCK_VOCABULARIES)));
          $file_node = file_node_create((array)$file_node);
          $nid = $file_node->nid;
        }
        else {
          if ($nid = db_result(db_query('SELECT '. $db_info['columns']['value']['column'] .' FROM {'. $db_info['table'] .'} WHERE vid = %d'. ($multiple ? ' AND delta = '. $o : ''), isset($node->old_vid) ? $node->old_vid : $node->vid))) {
            $file_node = node_load($nid);
            if (node_access('update', $file_node)) {
              if (FILE_CCK_OG_INHERITANCE)
                _file_propagate_og($node, $file_node);
              _file_propagate_terms($node, $file_node, array('all' => FILE_CCK_VOCABULARIES_ALL, 'og' => FILE_CCK_OG_VOCABULARIES, 'vocabs' => unserialize(FILE_CCK_VOCABULARIES)));
              node_save($file_node);
            }
          }
          $nid = $nid ? $nid : NULL;
        }

        if (isset($nid) || $field['multiple'] != 1) {
          _file_cck_db_update(array('action' => 'update', 'table' => $db_info['table'], 'column' => $db_info['columns']['value']['column'], 'multiple' => $multiple, 'delta' => $delta, 'file_nid' => $nid, 'nid' => $node->nid, 'vid' => $node->vid));
          $delta++;
        }
      }
      break;
  }
}

/**
 * Implementation of hook_content_is_empty().
 */
function file_cck_content_is_empty($item, $field) {
  if (empty($item['value'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implementation of hook_field_formatter_info().
 */
function file_cck_field_formatter_info() {
  return array(
    'default' => array('label' => t('first handler'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'file' => array('label' => t('file'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'preview' => array('label' => t('preview'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'preview_file' => array('label' => t('preview and file'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'preview_formats' => array('label' => t('preview and other formats'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'preview_formats_file' => array('label' => t('preview, other formats and file'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'thumbnail' => array('label' => t('thumbnail'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'thumbnail_open' => array('label' => t('thumbnail with an open link'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
    'thumbnail_download' => array('label' => t('thumbnail with a download link'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('file_cck')),
  );
}

/**
 * Proxy theme function for file_cck field formatters
 */
function theme_file_cck_formatter_generic($element) {
  $fid = $element['#item']['value'];

  if (preg_match('/^s_/', $fid)) {
    $file = $_SESSION['file_cck_files'][$fid];
    $node = (object)array('file' => $file);
    $id = preg_replace('/^s_/', '', $fid);
  }
  else if (is_numeric($fid)) {
    $node = node_load($fid);
    $file = $node->file;
    if (!is_object($file) || !node_access('view', $node))
      return '';

    $file->name = $node->title;
    $id = $fid;
  }
  else {
    return '';
  }

  switch ($element['#formatter']) {
    case 'file':
      $data = array(
        'file' => theme('file_render', $file),
      );
      return theme('file_show', $data);
      break;
    case 'preview':
      $data = array(
        'preview' => FILE_SHOW_PREVIEWS ? file_render_previews($file, 'files_'. $id) : '',
      );
      return theme('file_show', $data);
      break;
    case 'preview_file':
      $data = array(
        'preview' => FILE_SHOW_PREVIEWS ? file_render_previews($file, 'files_'. $id) : '',
        'file' => theme('file_render', $file),
      );
      return theme('file_show', $data);
      break;
    case 'preview_formats':
      $data = array(
        'preview' => FILE_SHOW_PREVIEWS ? file_render_previews($file, 'files_'. $id) : '',
        'generated' => FILE_SHOW_GENERATED ? file_render_generated($file, 'files_'. $id) : '',
      );
      return theme('file_show', $data);
      break;
    case 'preview_formats_file':
      $data = array(
        'preview' => FILE_SHOW_PREVIEWS ? file_render_previews($file, 'files_'. $id) : '',
        'generated' => FILE_SHOW_GENERATED ? file_render_generated($file, 'files_'. $id) : '',
        'file' => theme('file_render', $file),
      );
      return theme('file_show', $data);
      break;
    case 'thumbnail':
      if (defined('FILE_IMAGE_THUMBNAIL_RESOLUTION'))
        $thumbnail = file_get_image($file, 'file_image_thumbnail', explode('x', FILE_IMAGE_THUMBNAIL_RESOLUTION));
      return isset($thumbnail) ? $thumbnail : t('no thumbnail');
      break;
    case 'thumbnail_open':
      if (defined('FILE_IMAGE_THUMBNAIL_RESOLUTION'))
        $thumbnail = file_get_image($file, 'file_image_thumbnail', explode('x', FILE_IMAGE_THUMBNAIL_RESOLUTION));
      $thumbnail = isset($thumbnail) ? $thumbnail : t('no thumbnail');
      return !empty($file->nid) ? l($thumbnail, 'node/'. $file->nid, array('html' => TRUE)) : $thumbnail;
      break;
    case 'thumbnail_download':
      if (defined('FILE_IMAGE_THUMBNAIL_RESOLUTION'))
        $thumbnail = file_get_image($file, 'file_image_thumbnail', explode('x', FILE_IMAGE_THUMBNAIL_RESOLUTION));
      $thumbnail = isset($thumbnail) ? $thumbnail : t('no thumbnail');
      return !empty($file->nid) ? l($thumbnail, 'file/'. $file->nid .'/download/'. $file->vid, array('html' => TRUE)) : $thumbnail;
      break;
    case 'default':
    default:
      $file_handlers = file_handlers_for($file);
      foreach (file_get_mime_handlers() as $handler => $data) {
        if (array_key_exists($handler, $file_handlers) && $data['enabled'] == 1 && function_exists($handler .'_render')) {
          break;
        }
      }
      return isset($handler) ? file_wrapper_html($node, $handler) : '';
      break;
  }
}

/**
 * Returns the file links section.
 *
 * @param $file
 *   A file object as returned from file_node_load().
 * @param $id
 *   ID of the preview in case there are several previews on the same page.
 *
 * @return
 *   The HTML section of the file formats.
 */
function file_cck_render_links($file, $id = 0) {
  if (empty($file->nid))
    return '';

  $output = '<div class="file-links" id="file_formats_'. $id .'">';
  $links = l('open', 'node/'. $file->nid) . (FILE_VIEW_LINK ? ' | '. l('view', 'file/'. $file->nid .'/view/'. $file->vid) : '') .' | '. l('download', 'file/'. $file->nid .'/download/'. $file->vid);
  $output .= t('File') .' '. (isset($file->name) ? '"'. check_plain($file->name) .'" ': '') . t('options') .': '. $links;
  $output .= '</div>';
  return $output;
}

/**
 * Implementation of hook_widget_info().
 */
function file_cck_widget_info() {
  return array(
    'file_cck_file' => array(
      'label' => t('File'),
      'field types' => array('file_cck'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}

/**
 * Implementation of hook_widget().
 */
function file_cck_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $element = array(
    '#type' => $field['widget']['type'],
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
  );
  return $element;
}

/**
 * Implementation of hook_widget_settings().
 */
function file_cck_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
      $form = array();
      $form['extensions_allowed'] = array(
        '#type' => 'textfield',
        '#title' => t('Allowed file extensions'),
        '#default_value' => isset($widget['extensions_allowed']) ? $widget['extensions_allowed'] : '',
        '#description' => t('Only files with those extensions will be possible to upload to the website. Separate extensions with a space and do not include the leading dot. Empty means that all file types are allowed.') . (module_exists('file_restriction') ? ' '. l(t('Restrictions'), 'admin/settings/file/restriction') . t(' module\'s extension restrictions are also applied.') : ''),
      );
      return $form;
    case 'validate':
      break;
    case 'save':
      return array('extensions_allowed');
  }
}

//////////////////////////////////////////////////////////////////////////////
// FAPI hooks

/**
 * Implementation of FAPI hook_elements().
 */
function file_cck_elements() {
  return array(
    'file_cck_file' => array(
      '#input' => TRUE,
      '#columns' => array('value'),
      '#delta' => 0,
      '#process' => array('file_cck_file_process'),
    ),
  );
}

/**
 * FAPI theme for an individual text elements.
 */
function theme_file_cck_file($element) {
  return $element['#children'];
}

/**
 * Process an individual element.
 */
function file_cck_file_process($element, $edit, $form_state, $form) {
  $delta = $element['#delta'];

  if (module_exists('file_restriction')) {
    $extensions_allowed = trim(isset($form['#field_info'][$element['#field_name']]['widget']['extensions_allowed']) ? $form['#field_info'][$element['#field_name']]['widget']['extensions_allowed'] : '');
    file_restriction_js(strtr('#edit-'. $element['#field_name'] .'-'. $delta .'-value', '_', '-'), !empty($extensions_allowed) ? array('extensions_allowed' => $extensions_allowed) : array());
  }

  $element['value'] = array(
    '#name' => 'files['. $element['#field_name'] .'_'. $delta .']',
    '#type' => 'file',
    '#title' => $element['#title'],
    '#description' => $element['#description'],
    '#required' => $element['#required'],
    '#default_value' => NULL,
  );
  return $element;
}

//////////////////////////////////////////////////////////////////////////////
// Ahah uploads

/**
 * Menu callback for AHAH additions.
 */
function file_cck_js() {

  // Load the form from the Form API cache.
  $cache = cache_get('form_'. $_POST['form_build_id'], 'cache_form');
  $form = $cache->data;
  $form_state = array('values' => $_POST);

  // Handle new uploads and merge tmp files into node-files.
  file_cck_form_submit($form, $form_state);

  // Build a replacement form.
  $field_form = _file_cck_form($form, $form_state);

  $js = '';
  if (module_exists('file_restriction')) {
    $extensions_allowed = trim(isset($field_form['#field_info']['widget']['extensions_allowed']) ? $field_form['#field_info']['widget']['extensions_allowed'] : '');
    $delta = 0;
    while ($delta < $field_form['#field_count']) {
      $js .= file_restriction_js(strtr('#edit-'. $field_form['#field_info']['field_name'] .'-'. $delta .'-value', '_', '-'), !empty($extensions_allowed) ? array('extensions_allowed' => $extensions_allowed) : array(), TRUE);
      $delta++;
    }
  }

  // Save the altered cache.
  $cache->data = $form;
  cache_set('form_'. $_POST['form_build_id'], $cache->data, 'cache_form', $cache->expire);

  // Render the form for output.
  $field_form += array(
    '#post' => $_POST,
    '#programmed' => FALSE,
    '#tree' => FALSE,
    '#parents' => array(),
  );

  drupal_alter('form', $field_form, array(), 'file_cck_js');
  $form_state = array('submitted' => FALSE);
  $field_form = form_builder('file_cck_js', $field_form, $form_state);
  $output = theme('status_messages') . drupal_render($field_form) . $js;

  // We send the updated cck file form.
  // Don't call drupal_json(). ahah.js uses an iframe and
  // the header output by drupal_json() causes problems in some browsers.

  print drupal_to_js(array('status' => TRUE, 'data' => $output));
  exit;
}

/**
 * Save new uploads and store them in the session to be associated to the node.
 *
 * @param $form
 *   A form array.
 * @param $form_state
 *   User ubmitted data.
 */
function file_cck_form_submit($form, &$form_state) {

  // Clicked submit button name is sent only when no file is uploaded.
  // Oterwise hidden form element is used.
  foreach ($form_state['values'] as $k => $v) {
    if ($v == t('Add another item')) {
      $field_name = str_replace('_add_more', '', $k);
      break;
    }
  }
  $field_name = isset($field_name) ? $field_name : $form_state['values']['file_cck_current_field'];
  $form_state['values']['file_cck_current_field'] = $field_name;

  $type = content_types($form['#node']->type);
  $field = $type['fields'][$field_name];

  $i = 0;
  do {
    if ($upload = file_save_upload($field_name .'_'. $i, array_merge(file_get_validators(), array('file_cck_upload_validate' => array(isset($field['widget']['extensions_allowed']) ? $field['widget']['extensions_allowed'] : ''))))) {
      $node = (object)array('nosave' => TRUE);
      if (!file_node_save($node, $upload)) {
        drupal_set_message(t("Error saving file %file. Please, contact site administrator.", array('%file' => $upload->filename)), 'error');
      }
      else {

        // Find the next session id and save it with the uploaded file.
        // Remove 's_' when calculating next key.
        $node->file->sid = 's_'. $field_name .'_'. $i;
        $node->file->field = $field_name;
        $node->file->delta = $i;
        _file_cck_file($form_state, $node->file);
      }
    }
    $i++;
  } while (array_key_exists($i, $form_state['values'][$field_name]));
}

/**
 * Attaches a file to the current files list.
 *
 * @param $form_state
 *   A reference to the form state array.
 * @param $file
 *   A file object to be attached.
 */
function _file_cck_file(&$form_state, $file) {
  // We do not allow the same file to be uploaded several times.
  $is_uploaded = FALSE;
  if (isset($form_state['values']['files'])) {
    foreach ($form_state['values']['files'] as $fid => $file_attached) {
      if ($file_attached['uri'] == $file->uri && $file_attached['field'] == $file->field) {
        $is_uploaded = TRUE;
        drupal_set_message(t("The file %file is already attached.", array('%file' => $file->name)), 'notice');
        break;
      }
    }
  }
  if (!$is_uploaded) {
    // Add default list and weight parameters.
    $file->weight = isset($form_state['values']['files']) ? count($form_state['values']['files']) + 1 : 1;

    $fid = isset($file->sid) ? $file->sid : 'n_'. $file->nid;
    $_SESSION['file_cck_files'][$fid] = $file;
    $form_state['values']['files'][$fid] = (array)$file;
  }
}

/**
 * Generates form inside cck field.
 *
 * @param $form
 *   A form array
 * @param $files
 *   An array of the file objects
 *
 * @return
 *   A form array.
 */
function _file_cck_form(&$form, &$form_state) {
  $field = $form_state['values']['file_cck_current_field'];
  $group = module_exists('fieldgroup') ? _fieldgroup_field_get_group($form['#node']->type, $field) : NULL;

  $field_form = $group ? $form[$group][$field] : $form[$field];
  $count = count(array_filter($field_form, create_function('$a', 'return is_array($a) && isset($a[\'#type\']) && $a[\'#type\'] == \'file_cck_file\';')));

  // Arrange the files. To add a correct weight of the new field, the current weights should be not positive.
  foreach ($form_state['values'][$field] as $delta => $data) {
    $field_form[$delta]['#weight'] = $data['_weight'];
    $field_form[$delta]['_weight']['#default_value'] = $data['_weight'];

    $field_form[$delta]['_weight']['#delta'] = $count;
  }

  // Add a notice that the file has been uploaded.
  if (isset($form_state['values']['files'])) {
    foreach ($form_state['values']['files'] as $fid => $file) {
      $file = (object)$file;
      if (isset($file->field) && $file->field == $field)
        $field_form[$file->delta]['#prefix'] = t('A file %file has already been uploaded. If you upload another file the current file data will be replaced.', array('%file' => $file->name));
    }
  }

  // Create a new form element and reset default values.
  $field_form[$count] = $field_form[0];
  $field_form[$count]['#default_value']['value'] = 0;
  $field_form[$count]['_weight']['#default_value'] = $count;
  $field_form[$count]['#weight'] = $count;
  $field_form[$count]['#delta'] = $count;
  unset($field_form[$count]['#prefix']);

  if ($group)
    $form[$group][$field] = $field_form;
  else
    $form[$field] = $field_form;

  return array($field => $field_form, '#field_info' => $form['#field_info'][$field], '#field_count' => $count + 1);
}

//////////////////////////////////////////////////////////////////////////////
// Auxiliary functions

/**
 * Since hook_field() does not allow changing of the field value and
 * direct writes to the database are overwritten in the content.module,
 * here we accumulate the field change sqls which are executted later.
 *
 * @param $sql
 *   SQL statement to be executed after hook_field() is over.
 * @param $args
 *   An array of arguments to be passed to the sql.
 */
function _file_cck_db_update($action = array()) {
  static $actions = array();
  if (!empty($action)) {
    $actions[] = $action;
  }
  else {
    foreach ($actions as $action) {
      switch ($action['action']) {
        case 'update':
          $sql = 'SELECT nid FROM {'. $action['table'] .'} WHERE vid = %d'. ($action['multiple'] ? ' AND delta = %d' : '');
          $args = array_merge(array($action['vid']), $action['multiple'] ? array($action['delta']) : array());
          if (db_result(db_query($sql, $args))) {
            if (isset($action['file_nid']))
              db_query('UPDATE {'. $action['table'] .'} SET '. $action['column'] .' = %d WHERE vid = %d'. ($action['multiple'] ? ' AND delta = %d' : ''), array_merge(array($action['file_nid']), $args));
            else
              db_query('UPDATE {'. $action['table'] .'} SET '. $action['column'] .' = null WHERE vid = %d'. ($action['multiple'] ? ' AND delta = %d' : ''), $args);
          }
          else {
            if (isset($action['file_nid']))
              db_query('INSERT INTO {'. $action['table'] .'} ('. $action['column'] .', nid, vid'. ($action['multiple'] ? ', delta' : '') .') VALUES (%d, %d, %d'. ($action['multiple'] ? ', %d' : '') .')', array_merge(array($action['file_nid'], $action['nid']), $args));
            else
              db_query('INSERT INTO {'. $action['table'] .'} ('. $action['column'] .', nid, vid'. ($action['multiple'] ? ', delta' : '') .') VALUES (null, %d, %d'. ($action['multiple'] ? ', %d' : '') .')', array_merge(array($action['nid']), $args));
          }
          break;
        case 'remove':
          $sql = 'UPDATE {'. $action['table'] .'} SET '. $action['column'] .' = null WHERE vid = %d'. ($action['multiple'] ? ' AND delta = %d' : '');
          $args = array_merge(array($action['vid']), $action['multiple'] ? array($action['delta']) : array());
          db_query($sql, $args);
          break;
        case 'delete':
          $sql = 'DELETE FROM {'. $action['table'] .'} WHERE vid = %d'. ($action['multiple'] ? ' AND delta = %d' : '');
          $args = array_merge(array($action['vid']), $action['multiple'] ? array($action['delta']) : array());
          db_query($sql, $args);
          break;
      }
    }
    $actions = array();
  }
}

/**
 * A file upload validation function.
 *
 * @param $file
 *   A file object.
 * @param $extensions
 *   A space separated list of allowed extensions.
 *
 * @return
 *   An array with an error messages.
 */
function file_cck_upload_validate($file, $extensions = '') {
  $extensions = trim($extensions);
  return !empty($extensions) ? file_validate_extensions($file, $extensions) : array();
}

