<?php
// $Id: filefield.module,v 1.10 2006/09/06 23:55:49 dopry Exp $

/**
 * @file
 * Defines an image field type.
 *  uses content.module to store the fid, and the drupal files table to store the actual file data.
 *
 * @todo
 *   - access control
 *   - multiple image support for views integration. -- stange territory.
 *   @hurrah!!
 */

function filefield_init () {
  //drupal_set_message('POST: <pre>'. print_r($_POST, true) .'</pre>');
}

/**
 * Implementation of hook_help().
 */
function filefield_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Defines a file  field type for content.module.');
  }
}

function filefield_menu($maycache) {
  $items = array();

  // Add handlers for previewing new uploads.
  if ($_SESSION['filefield']) {
    foreach ($_SESSION['filefield'] as $fieldname => $files) {
      foreach($files as $delta => $file) {
        $filename = file_create_filename($file['filename'], file_create_path());
        // strip file_directory_path() from private downloads path. @see file_create_url
        if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) ==  FILE_DOWNLOADS_PRIVATE) {
          if (strpos($filename, file_directory_path()) !== false) {
            $filename = trim(substr($filename, strlen(file_directory_path())), '\\/');
          }
          $filename = 'system/files/' . $filename;
        }
        $items[] = array(
          'path' => $filename, 'title' => t('file download'),
          'callback' => '_filefield_preview',
          'access' => TRUE,
          'type' => MENU_CALLBACK
        );
        $file['preview'] = $filename;
        $_SESSION['filefield'][$fieldname][$delta] = $file;        
      }
    }
  }
  //drupal_set_message('SESSION @ hook_menu: <pre>'. print_r($_SESSION['filefield'], true) .'</pre>');
  return $items;
}

/**
 *  transfer a file that is in a 'preview' state.
 *  @todo  multiple support
 */
function _filefield_preview() {
  foreach ($_SESSION['filefield'] as $fieldname => $files) {
    foreach ($files as $delta => $file) {
      if ($file['preview'] == $_GET['q']) {
        file_transfer($file['filepath'], array('Content-Type: '. mime_header_encode($file['filemime']),
                                           'Content-Length: '. $file['filesize']));
        exit();
      }
    }
  }
}

/**
 * Implementation of hook_field_info().
 */
function filefield_field_info() {
  return array(
    'file' => array('label' => 'file'),
  );
}

/**
 * Implementation of hook_field_settings().
 */
function filefield_field_settings($op, $field) {
  switch ($op) {
    case 'form':
      $form = array();
      return $form;
    case 'validate':

      break;
    case 'save':
      return array();

    case 'database columns':
      $columns = array(
        'fid' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'),
        'description' => array('type' => 'varchar', length => 255, 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
        'list' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'),
      );
      return $columns;
  }
}

/**
 * insert a file into the database.
 * @param $node
 *    node object file will be associated with.
 * @param $file
 *    file to be inserted, passed by reference since fid should be attached.
 *    
 */
function filefield_file_insert($node, &$file, $field) {
  $fieldname = $field['field_name'];
  if ($file = file_save_upload((object)$file, file_directory_path() . '/'.$file['filename'])) {
    $file = (array)$file;
    $file['fid'] = db_next_id('{files}_fid');
    db_query('INSERT into {files} (fid, nid, filename, filepath, filemime, filesize)  
             VALUES (%d, %d, "%s","%s","%s",%d)',
            $file['fid'], $node->nid, $file['filename'], $file['filepath'], $file['filemime'], $file['filesize']);
    return (array)$file;
  }
  else {
    // Include file name in upload error.
    form_set_error(NULL, t('file upload was unsuccessful.'));
    return FALSE;
  }
}


/**
 * update the file record if necessary
 * @param $node
 * @param $file
 * @param $field
 */
function filefield_file_update($node, &$file, $field) {
  $file = (array)$file; 
  //drupal_set_message(' filefield_file_update: <pre>'. print_r($file, true) .'</pre>');
  if ($file['remove'] == TRUE) {
     _filefield_file_delete($file, $field['field_name']);
     return NULL;
  }
  if ($file['fid'] == 'upload') {  
    return filefield_file_insert($node, $file, $field);
  }
  else {
    // if fid is not numeric here we should complain.
    // else we update the file table.  
  }
  return $file;
}

/**
 * Implementation of hook_field().
 */
function filefield_field($op, $node, $field, &$node_field, $a1, $a2) {
  //drupal_set_message('pre hook_field: '.$op.'<pre>'.print_r($node_field, true).'</pre>');
  $fieldname = $field['field_name'];
  switch ($op) {
    // called after content.module loads default data.
    case 'load':
      $values = array();
      foreach ($node_field as $delta => $file) {
        //drupal_set_message('loading file form content.module: <pre>'. print_r($file, true) .'</pre>');
        $dbfile =  _filefield_file_load($file['fid']);
        //drupal_set_message('loading file from db: <pre>'. print_r($dbfile, true) .'</pre>');
        if (!empty($file)) {
          $values[$delta]  = array_merge($file, $dbfile);
          //$node_field[$delta] = $values[$delta];
        }
      }
      //drupal_set_message('post: hook_field: '.$op.'<pre>'.print_r($node_field, true).'</pre>');
      $output = array($fieldname => $values);
      break; 

    case 'view':
      $files = array();
      foreach ($node_field as $delta => $item) {
        //drupal_set_message('<pre>'. print_r($item, true) .'</pre>');
        // do not show images flagged for deletion in preview.
        if (!$item['remove']  && $item['list']) {
          $node_field[$delta]['view'] = theme('filefield', $item); 
          $files[] =  $node_field[$delta];
        }
      }
      $view = theme('field', $node, $field, $files, $a1, $a2);
      
      $output = '<div class="'. $field['field_name'] .'">'. $view .'</div>';
      break;

    // called before content.module defaults.
    case 'insert':
      foreach ($node_field as  $delta => $item) {
        $node_field[$delta] = filefield_file_insert($node, $item, $field); 
      }
      break;

    // called before content.module defaults.
    case 'update':
      foreach ($node_field as $delta => $item) {
        $node_field[$delta] = filefield_file_update($node, $item, $field); 
      }
      break;

    case 'delete':
      foreach ($node_field as $delta => $item) {
        _filefield_file_delete($item, $field['field_name']); 
      }
      break;
  }
  //drupal_set_message('post hook_field: '.$op.'<pre>'.print_r($node_field, true).'</pre>');
  return $output;
}

/**
 * Implementation of hook_widget_info().
 */
function filefield_widget_info() {
  return array(
    'file' => array(
      'label' => 'File',
      'field types' => array('file'),
    ),
  );
}

/**
 * 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' => $widget['file_extensions'] ? $widget['file_extensions'] : 'txt',
        '#size' => 64, 
        '#maxlength' => 64, 
        '#description' => t('Extensions a user can upload to this field. Seperate extensions with a space and do not include the leading dot.')
      );
      return $form;
    case 'validate':
      break;
    case 'save':
      return array('file_extensions');
  }
}


function filefield_clear_session() {
  if (is_array($_SESSION['filefield']) && count($_SESSION['filefield'])) {
    foreach (array_keys($_SESSION['filefield']) as $fieldname) {
      filefield_clear_field_session($fieldname);
    }
    unset($_SESSION['filefield']);
  }
}

function filefield_clear_field_session($fieldname) {
  if (count($_SESSION['filefield'][$fieldname])) {
    foreach ($_SESSION['filefield'][$fieldname] as $files) {
      foreach ($files as $delta => $file) {
        if (is_file($file['filepath'])) {
          file_delete($file['filepath']);
        }
      }
    }
    unset($_SESSION['filefield'][$fieldname]);
  }
}

function _filefield_file_delete($file, $fieldname) {
  //drupal_set_message(' _filefield_file_delete: <pre>'. print_r($file, true) .'</pre>');
  if (is_numeric($file['fid'])) {
    db_query('DELETE FROM {files} WHERE fid = %d', $file['fid']);
  }
  else {
    unset($_SESSION['filefield'][$fieldname][$file['sessionid']]);
  }
  return file_delete($file['filepath']);
}

/**
 * Implementation of hook_widget().
 */
function filefield_widget($op, $node, $field, &$node_field) {
  //drupal_set_message('pre filefield_widget: '. $op .'<pre>'. print_r($node_field, true) .'</pre>');
  $fieldname = $field['field_name'];
  switch ($op) {
    case 'prepare form values':
     // drupal_set_message('pre filefield_widget: '. $op .'<pre>'. print_r($node_field, true) .'</pre>');
      //drupal_set_message('pre filefield_widget node: '. $op .'<pre>'. print_r($node, true) .'</pre>');
      // clean up the session if we weren't posted.
      if (!count($_POST)) {
        filefield_clear_session();
      }

      // Attach new files 
      if ($file = file_check_upload($fieldname . '_upload')) {
        //drupal_set_message('file found!!!');
        $file = (array)$file;
        $ext = array_pop(explode('.',$file['filename']));
        $allowed_extensions = array_unique(explode(' ', trim($field['widget']['file_extensions'])));
        if (in_array($ext, $allowed_extensions)) { 
          //do mime specific handling.
        }
        else {
          form_set_error($field['field_name'] .'_upload',t('Files with the extension %ext are not allowed. Please upload a file with an extension from the following list: %allowed_extensions', array('%ext' => theme('placeholder',$ext), '%allowed_extensions' => theme('placeholder', $field['widget']['file_extensions']))));
          return FALSE;
        }
        $file['fid'] = 'upload';
        if (!$field['multiple']) {
          // Remove old temporary file from session.
          filefield_clear_field_session($fieldname);
        }
        $file['sessionid'] = count($_SESSION['filefield'][$fieldname]);
        $_SESSION['filefield'][$fieldname][$file['sessionid']] = $file;
       // drupal_set_message('SESSION: <pre>'. print_r($_SESSION['filefield'], true) .'</pre>');
      }
        

      
      // Load files from preview state. before committing actions.
      /* commented out until cck prepare form values / node_field situation is worked out 
      if (is_array($_SESSION['filefield'][$fieldname]) && count($_SESSION['filefield'][$fieldname])) {
        foreach($_SESSION['filefield'][$fieldname] as $delta => $file) {
          $node_field[] = $file;
        }
      }
      */
      break;

    case 'form':
      if (is_array($_SESSION['filefield'][$fieldname]) && count($_SESSION['filefield'][$fieldname])) {
        foreach($_SESSION['filefield'][$fieldname] as $delta => $file) {
          $node_field[] = $file;
        }
      }
      
      $form = _filefield_widget_form($node, $field, $node_field);
      return $form;

    case 'validate':
      if ($field['required']) {
        if (!count($node_field)) {
          form_set_error($fieldname, $field['widget']['label'] .' is required.');
        }
      }
      return;

    case 'process form values':
      //drupal_set_message('<pre>'. print_r($node, true) .'</pre>');
      foreach ($node_field as $delta => $item) {
        if ($item['fid'] == '0') {
          $node_field[$delta] = NULL;
        }
      }
      return; 

  }
  //drupal_set_message('post filefield_widget: '. $op .'<pre>'. print_r($node_field, true) .'<pre>');
}

function _filefield_widget_form($node, $field, &$node_field) {
  $fieldname = $field['field_name'];
  theme_add_style(drupal_get_path('module', 'filefield') .'/filefield.css');
 
  $form = array(); 
  $form[$fieldname] = array(
    '#type' => 'fieldset',
    '#title' => t($field['widget']['label']),
    '#weight' => 5,
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#tree' => TRUE,
    '#theme' => 'filefield_current',
  );
  
  $form[$fieldname][$fieldname .'_upload'] = array(
    '#type'  => 'file',
    '#description' => t('Attach a file. allowed extensions(%ext)', array('%ext' => theme('placeholder', $field['widget']['file_extensions']))),
    '#weight' => 9,
    '#tree' => FALSE,
  );

  $form[$fieldname]['Update'] = array(
    '#type' => 'button',
    '#value' => t('Update'),
    '#name' => 'cck_filefield_'.$fieldname.'_op',
    '#attributes' => array('id' => $fieldname.'-attach-button'),
    '#tree' => FALSE,
    '#weight' => 10,
  );

  //drupal_set_message('form: <pre>'. print_r($form, TRUE) .'</pre>');
  //drupal_set_message('filefield_widget_form: nodefield: <pre>'. print_r($node_field, true) .'</pre>');
  if (is_array($node_field) && count($node_field)) {
    foreach($node_field as $delta => $file) {
      if (!empty($file))  {
        $form[$fieldname][$delta]['icon'] = array(
          '#type' => 'markup',
          '#value' => theme('filefield_icon', $file),
        );
      
        if (strpos($file['fid'], 'upload') === false) {
          $filepath =  $file['filepath'];
        }
        else {
          $filepath =  file_create_filename($file['filename'], file_create_path());
        }
        $description = file_create_url($filepath);      
        $description = "<small>". check_plain($description) ."</small>";
        $form[$fieldname][$delta]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file['description'])) ? $file['description'] : $file['filename'], '#maxlength' => 256, '#description' => $description );
        $form[$fieldname][$delta]['size'] = array('#type' => 'markup', '#value' => format_size($file['filesize']));
        $form[$fieldname][$delta]['remove'] = array('#type' => 'checkbox', '#default_value' => $file['remove']);
        $form[$fieldname][$delta]['list'] = array('#type' => 'checkbox',  '#default_value' => $file['list']);

        $form[$fieldname][$delta]['filename'] = array('#type' => 'value',  '#value' => $file['filename']);
        $form[$fieldname][$delta]['filepath'] = array('#type' => 'value',  '#value' => $file['filepath']);
        $form[$fieldname][$delta]['filemime'] = array('#type' => 'value',  '#value' => $file['filemime']);
        $form[$fieldname][$delta]['filesize'] = array('#type' => 'value',  '#value' => $file['filesize']);
        $form[$fieldname][$delta]['fid'] = array('#type' => 'value',  '#value' => $file['fid']);
      
        // Special handling for single value fields
        // mark original item for deletion.
        if (!$field['multiple'] && $delta == 0 && (count($_SESSION['filefield'][$fieldname]) > 1)) {
          $form[$fieldname][$delta]['remove']['#value'] = 1;
          $form[$fieldname][$delta]['replace'] = array(
            '#type' => 'markup',
            '#value' => t('If a new file is uploaded, the current file will be replaced upon submitting the node edit form.'),
          );
        }
      }
    }
    // end foreach...
  }
  //drupal_set_message('filefield_form('. $fieldname .'): <pre>'. print_r($form, TRUE) .'</pre>');
  return $form;
}

function theme_filefield_current(&$form) {
  $header = array(t('Type'), t('Description'), t('Size'),  t('List'), t('Delete'));
  //drupal_set_message('form: <pre>'. print_r($form, TRUE) .'</pre>');

  foreach (element_children($form) as $key) {
    //drupal_set_message($key);
    //only display numerically keyed fields in form.
    //numeric key == $delta
    if (is_numeric($key)) {
      $row = array();
      $row[] = form_render($form[$key]['icon']);
      $row[] = form_render($form[$key]['description']);
      $row[] = form_render($form[$key]['size']);
      $row[] = form_render($form[$key]['list']);
      $row[] = form_render($form[$key]['remove']);
      $rows[] = $row;
    }
  }
  if (count($rows)) {
    $output = theme('table', $header, $rows);
  }
  $output .= form_render($form);
  return $output;
}


function filefield_field_view_item($field, $node_field_item) {
    $file = _filefield_file_load($node_field_item['fid']);
    return theme('filefield_view_file', $file, $node_field_item['description']); 
}


function _filefield_file_load($fid = NULL) {
  // Exit if we were not passed an fid to load;
  if (!isset($fid)) { 
    //drupal_set_message('_filefield_file_load did not recieve an fid');
    return false;
  }
  // Test to catch fid, eventuall plan to have node_load syntax
  // once file_attributes table is complete
  if (is_numeric($fid)) {
    $result = db_query('SELECT * FROM {files} WHERE fid = %d', $fid);
    return  db_fetch_array($result);
  }
  //drupal_set_message('expected file not found.');
  return FALSE;
}

function theme_filefield_icon($file) {
  $ext = array_pop(explode('.',$file['filename']));
  $known_extensions = array('0','ace','aif','ai','ani','asf','asp','avi','bak','bat','bin','bmp','bz2','bz','cab','cdr','cfg','com','conf','cpt','css','cur','dat','db','dcr','dic','diff','dir','dll','dmg','doc','dwg','edir','eml','eps','exe','fla','flv','fon','gif','gz','hqx','html','htm','ico','inc','ini','iso','jpeg','jpg','js','lnk','log','m3u','mdb','midi','mid','mov','mp3','mpeg','mpg','nfo','odb','odc','odf','odg','odm','odp','ods','odt','ogg','otg','oth','otp','ots','ott','patch','pdf','php3','php','phtml','pl','png','pps','ppt','psd','pwl','qt','ram','ra','rar','reg','rpm','rtf','sh','shtml','sit','sql','svg','swf','sxc','sxi','sxw','sys','tar','tgz','tiff','tif','tmp','tpl','ttf','txt','wav','wma','wmv','wp','xls','xml','zip');
  if (!in_array($ext, $known_extensions))  {
    $ext = 0; 
  }
  $imagepath = drupal_get_path('module','filefield') .'/ico/'. $ext .'.png';


  
  return '<div class="filefield-icon-container"><div class="filefield-icon field-icon-'. $ext .'"><img src="'. $imagepath .'" /></div></div>';
  //return '<div class="filefield-icon-container"><div class="filefield-icon field-icon-'. $ext .'">&nbsp;</div></div>'."\n";
}

function theme_filefield_view_file($file) {
  return theme('filefield', $file);
}

function theme_filefield($file) {
  $file = (array)$file;
  if (is_file($file['filepath'])) {
    if ($file['fid'] == 'upload') {
      $path = file_create_filename($file['filename'], file_create_path());
    }
    else {
      $path = $file['filepath'];
    }
    $url = file_create_url($path);
    $name = $file['filename'];
    $desc = $file['description'];
    return '<a href="'. check_url($url) .'">'.check_plain($name).'</a>';
  }
}
