<?php
/**
 * @file
 *
 * Uploadify module.
 */

/**
 * Implementation of hook_menu().
 */
function uploadify_menu() {
  $items['uploadify'] = array(
    'title' => 'uploadify',
    'page callback' => 'uploadify_post_page',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

  return $items;
}

/**
 * Menu callback.
 */
function uploadify_post_page() {
//  watchdog('uploadify files', print_r($_FILES, 1));
//  watchdog('uploadify post', print_r($_POST, 1));

  // Access control.
  if (empty($_POST['PHPSESSID']) || empty($_POST['form_id']) || empty($_POST['form_build_id'])) {
    // TODO
    drupal_json(array('data' => ''));
    exit;
  }

  // Session.
  $php_sess_id = $_POST['PHPSESSID'];
  if (session_id() != $php_sess_id) {
    session_destroy();
    session_id($php_sess_id);
    $_COOKIE[session_name()] = $php_sess_id;
    _drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
  }

  // Get a copy of form.
  $form_state = array('submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  $form = form_get_cache($form_build_id, $form_state);

  if (!$form) {
    // TODO
    drupal_json(array('data' => ''));
    exit;
  }

  $var = module_invoke_all($_POST['widget_type'] .'_uploadify_js', $form, $form_build_id, $form_state);

  print drupal_to_js($var);
  exit;
}

/**
 *  Implementation of hook_form_alter().
 */
function uploadify_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
    $node = $form['#node'];

    // CCK
    if (function_exists('content_types')) {
      $type = content_types($node->type);
      foreach ($type['fields'] as $field_name => $field) {
        if (!empty($field['widget']['uploadify_enabled'])) {
          drupal_alter($field['widget']['type'] .'_uploadify_form', $form, $field);
        }
      }
    }

    $form['#after_build'][] = 'uploadify_form_after_build';
  }
}

/**
 * Callback of #after_build.
 *
 * Is supposed to add JS & CSS.
 */
function uploadify_form_after_build($form, $form_state) {
  if (!empty($form['#uploadify_after_build'])) {
    foreach ($form['#uploadify_after_build'] as $field_name => $widget_type) {
      $form = module_invoke_all($widget_type .'_uploadify_after_build', $form, $form_state, $field_name);
    }
  }

  return $form;
}

/**
 * Implementation of hook_widget_settings_alter().
 */
function uploadify_widget_settings_alter(&$additions, $op, $widget) {
  switch ($op) {
    case 'form':
      if (!empty($widget['type'])) {
        $form = module_invoke_all($widget['type'] .'_settings_uploadify_form', $widget);
        $additions += $form;
      }

//      var_dump($additions, $widget);
      break;

    case 'save':
      if (!empty($widget['widget_type'])) {
        $settings_names = module_invoke_all($widget['widget_type'] .'_settings_uploadify_save', $widget);
        $additions = array_merge($additions, $settings_names);
      }

//      drupal_set_message(dprint_r($settings_names, 1));
//      drupal_set_message(dprint_r($additions, 1));
      break;
  }
}

/**
 * Implementation of hook_theme().
 */
function uploadify_theme() {
  return array(
    'uploadify_widget' => array(
      'arguments' => array('element' => NULL),
    ),
    'uploadify_element' => array(
      'arguments' => array('element' => NULL),
      'template' => 'uploadify-element',
    ),
  );
}

/**
 * Implementation of hook_elements().
 */
function uploadify_elements() {
  $elements = array();

  // For CCK.
  $elements['uploadify_widget'] = array(
    '#input' => TRUE,
    '#process' => array('uploadify_widget_process'),
  );

  return $elements;
}

/**
 * Form elements for CCK.
 */

/**
 * Element #process callback function.
 */
function uploadify_widget_process($element, $edit, &$form_state, $form) {
  $field = content_fields($element['#field_name'], $element['#type_name']);
  $name = "files[{$field['field_name']}]";
  $id = form_clean_id('uploadify-'. $field['field_name']);

  $element['#tree'] = FALSE;
  $element['#uploadify_id'] = $id;
  $element['#uploadify_name'] = $name;
  $element['#uploadify_hide'] = array();
  $element['#uploadify_replace'] = str_replace(array('_'), '-', $field['field_name'] .'-items');

  $element['#children'] = theme('uploadify_element', $element);

//  var_dump($field);
//  var_dump($element);
  return $element;
}

/**
 * Implementation of my hooks.
 */

/**
 * CCK Filefield.
 */

/**
 * Implementation of hook_[widget type]_uploadify_js().
 *
 * @see filefield_js().
 * @see content_add_more_js().
 *
 * 1. Match post with current form items.
 *   * Sort post and form items.
 *   * Erase extra post.
 *   * Find an empty form item, use as the target.
 *   * Find extra empty items, to make sure users can upload without my widget. TODO.
 *
 * 2. If no target, ask for a new empty form item.
 *   * If got, add it into form, cache form, treat it as the target.
 *   * If not, user meets a limitation. Trigger an error, stop the queue.
 *
 * 3. Save uploaded file to the target.
 *   * Rebuild form, add into next post, send back.
 *
 * 4. Attach new JS settings.
 */
function uploadify_filefield_widget_uploadify_js($form, $form_build_id, $form_state) {
  // Field.
  $field_name = $_POST['field_name'];
  $type_name = $_POST['type_name'];
  $field = content_fields($field_name, $type_name);
  if (empty($field)) {
    // TODO
    drupal_json(array('data' => ''));
    exit;
  }

  //
  // Stage 1.
  //
  $target_delta = NULL;
  $form_parents = $tree_parents = array();
  $element = uploadify_get_form_element($form, $field_name, $form_parents, $tree_parents);

  // Get post.
  $post_copy = $_POST;
  $post_uploadify = uploadify_array_get_nested_value($post_copy, $tree_parents);
  if (empty($post_uploadify)) {
    // TODO
    drupal_json(array('data' => ''));
    exit;
  }

  // Build values.
  // @see cck/includes/content.node_form.inc -> function content_add_more_js().
  $form_copy = $form;
  $form_state_copy = $form_state;
  $form_copy['#post'] = array();
  form_builder($_POST['form_id'], $form_copy, $form_state_copy);
  $form_state['values'] = $form_state_copy['values'];
  form_clean_id(NULL, TRUE);

  //
  // Sort.
  //
  // Get values.
  $values_copy = $form_state['values'];
  $values_uploadify = uploadify_array_get_nested_value($values_copy, $tree_parents);
  unset($values_uploadify[$field_name .'_add_more']);
  $max_delta = 0;
  $empty_delta = array();
  foreach ($post_uploadify as $delta => $item) {
    $values_uploadify[$delta]['_weight'] = $item['_weight'];
    // Erase extra post.
    // TODO: ?
    if (empty($values_uploadify[$delta])) {
      unset($post_uploadify[$delta]);
      continue;
    }
    // Find a target form. Only use the max delta.
    if ($delta > $max_delta) {
      $max_delta = $delta;
    }
    $determine_empty = $field['module'] .'_content_is_empty';
    if ($determine_empty($values_uploadify[$delta], $field) && $determine_empty($item, $field)) {
      $empty_delta[$delta] = $delta;
    }
  }
  if (array_key_exists($max_delta, $empty_delta)) {
    $target_delta = $max_delta;
  }

  // TODO: remove empty? if do, need to rebuild/cache form.

  // Sort.
  $values_uploadify = _content_sort_items($field, $values_uploadify);
  $post_uploadify = _content_sort_items($field, $post_uploadify);

  // Set values.
  uploadify_array_set_nested_value($form_state['values'], $tree_parents, $values_uploadify, TRUE);

  // Stage 2.
  $added_new = FALSE;
  if (!isset($target_delta)) {
    // Rebuild element.
    // @see cck/includes/content.node_form.inc -> function content_add_more_js().
    module_load_include('inc', 'content', 'includes/content.node_form');
    $form_state['item_count'] = array($field_name => count($post_uploadify) + 1);
    $form_element = content_field_form($form, $form_state, $field);
    // Some module expects form state to be a reference.
    // @see function drupal_alter().
    $data = &$form_element;
    $form_element_state = array();
    $data['__drupal_alter_by_ref'] = array(&$form_element_state);
    drupal_alter('form', $data, 'content_add_more_js');
    // My alter.
    drupal_alter($field['widget']['type'] .'_uploadify_form', $form_element, $field);

    // Find a target.
    $target_delta = max(array_keys($post_uploadify)) + 1;
    $added_new = TRUE;

    // Put into form. Also replace the $element we use later.
    $element = $form_element = $form_element[$field_name];

    // Set.
    uploadify_array_set_nested_value($form, $form_parents, $form_element, TRUE);

    // Cache.
    $form_state['values'] = array();
    form_set_cache($form_build_id, $form, $form_state);

    // Get default value.
    $default_value = $element[$target_delta]['#default_value'];

    // Assign to post.
    $post_uploadify = array_merge($post_uploadify, array($target_delta => $default_value));
  }

  // Set post.
  uploadify_array_set_nested_value($_POST, $tree_parents, $post_uploadify, TRUE);

  // Stage 3.
  // Assign the file.
  foreach ($_FILES['files'] as $key => $value) {
    if (is_array($value) && array_key_exists($field_name, $value)) {
      $_FILES['files'][$key][$field_name .'_'. $target_delta] = $value[$field_name];
      unset($_FILES['files'][$key][$field_name]);
    }
  }

  // Build the form. This saves the file.
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form['#post'] = $_POST;
  $form = form_builder($form_id, $form, $form_state);

  // Get new form element.
  foreach ($form_parents as $parent) {
    $form = $form[$parent];
  }
//  $element = uploadify_array_get_nested_value($form, $form_parents);

//  watchdog('final form', print_r($form, 1));
//  watchdog('final form keys', print_r(array_keys($form), 1));

  // Only return the table.
  // @see function theme_content_multiple_values().
  unset($form[$field_name .'_add_more'], $form['#description'], $form['#prefix'], $form['#suffix']);

  $output = drupal_render($form);

  // Status messages.
  $output .= theme('status_messages');

  // Send new uploadify settings.
  $settings = array();
  if ($added_new) {
    $settings += uploadify_get_js_settings($form, 'uploadify_'. $field['field_name'], array($target_delta));
  }
  if (!empty($settings)) {
    $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings.uploadify_'. $field['field_name'] .', '. drupal_to_js($settings) .');</script>';
  }

  // Send new AHAH settings.
  $settings = array();
  if ($added_new) {
    $settings += uploadify_get_js_settings($form[$target_delta], 'ahah', array('filefield_upload', 'filefield_remove'));
  }
  if (!empty($settings)) {
    $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings.ahah, '. drupal_to_js($settings) .');</script>';
  }

  return array('status' => TRUE, 'data' => $output);
}

/**
 * Implementation of hook_[widget type]_uploadify_form_alter().
 */
function uploadify_filefield_widget_uploadify_form_alter(&$form, $field) {
  $field_name = $field['field_name'];
  $form['#uploadify_after_build'][$field_name] = $field['widget']['type'];

  // Remove "add more".
  $add_more = $form[$field_name][$field_name .'_add_more'];
  if (empty($add_more)) {
    // TODO
    return;
  }
  // TODO: leave _add_more and add _uploadify.
  unset($form[$field_name][$field_name .'_add_more']);

  // Uploadify.
  $add_more['#type'] = 'uploadify_widget';
  unset($add_more['#value'], $add_more['#submit'], $add_more['#ahah']);

//  var_dump($form);
//  var_dump($field);
//  var_dump($add_more);
  $form[$field_name][$field_name .'_add_more'] = $add_more;
}

/**
 * Implementation of hook_[widget type]_uploadify_after_build().
 */
function uploadify_filefield_widget_uploadify_after_build($form, $form_state, $field_name) {
  $form_parents = $tree_parents = array();
  $element = uploadify_get_form_element($form, $field_name .'_add_more', $form_parents, $tree_parents);
  if (empty($element)) {
    // TODO
    return $form;
  }

  $field = content_fields($element['#field_name'], $element['#type_name']);
  $id = $element['#uploadify_id'];
  $name = $element['#uploadify_name'];
  $path = drupal_get_path('module', 'uploadify');

  // TODO: check result.
  $lib = uploadify_load_library();

  // Widget settings.
  switch ($field['widget']['uploadify_theme']) {
    case 'original':
      drupal_add_css($lib['path'] .'/uploadify.css');
      $auto = (bool) $field['widget']['uploadify_auto'];
      $cancel_img = url($lib['path'] .'/cancel.png', array('absolute' => TRUE, 'language' => language_default()));
      $hide_button = (bool) $field['widget']['uploadify_hide_button'];
      break;

    case 'custom':
      $auto = (bool) $field['widget']['uploadify_auto'];
      $cancel_img = url($field['widget']['uploadify_cancel_img'], array('absolute' => TRUE, 'language' => language_default()));
      $hide_button = (bool) $field['widget']['uploadify_hide_button'];
      break;

    default:
      drupal_add_css($path .'/css/uploadify_default.css');
      $auto = (bool) $field['widget']['uploadify_auto'];
      $cancel_img = url($path .'/images/cancel.png', array('absolute' => TRUE, 'language' => language_default()));
      $hide_button = TRUE;
      break;
  }

  if (!empty($id) && !empty($name)) {
    // Post data.
    $extra_post = array();
    $extra_post['field_name']  = $field['field_name'];
    $extra_post['type_name']   = $field['type_name'];
    $extra_post['widget_type'] = $field['widget']['type'];
    // Script data.
    $script_data = uploadify_build_script_data($form, $extra_post);

    // Settings used to retrieve form values.
    $uploadify_post = array();
    $form_parents = $tree_parents = array();
    $widget = uploadify_get_form_element($form, $field['field_name'], $form_parents, $tree_parents);
    foreach ($widget as $key => $value) {
      if (is_int($key) && isset($value['#id'])) {
        $uploadify_post[$value['#id']] = uploadify_get_post_id($widget, $key);
      }
    }

    // Get upload element (can be multiple), hide it later with JS.
//    $table_empty = TRUE;
//    $hide_class = str_replace(array('_'), '-', $field_name .'-hide-tr');
//    foreach (element_children($form[$field_name]) as $key) {
//      // TODO: wrong?
//      if (filefield_content_is_empty($form[$field_name][$key], $field)) {
//        $upload = $form[$field_name][$key];
//        $form[$field_name][$key]['#attributes'] = array('class' => $hide_class);
//        $add_more['#uploadify_hide']['uploadify-hide-tr'] = $hide_class;
//      }
//      else {
//        $table_empty = FALSE;
//      }
//    }
//    // Hide empty table.
//    if ($table_empty) {
//      unset($add_more['#uploadify_hide']['uploadify-hide-tr']);
//      $add_more['#uploadify_hide']['uploadify-hide-table'] = $hide_class;
//    }

    // Settings.
    $settings = array();
    $settings['uploadify'][$id] = array(
      'uploadifyHide'    => $element['#uploadify_hide'],
      'uploadifyReplace' => $element['#uploadify_replace'],

      // Pre-defined post.
      'scriptData'   => $script_data,

      // Uploadify.
      'uploader'     => url($lib['path'] .'/uploadify.swf', array('absolute' => TRUE, 'language' => language_default())),
      'script'       => url('uploadify', array('absolute' => TRUE)),
      'fileDataName' => $name,
      'cancelImg'    => $cancel_img,
      'hideButton'   => $hide_button,
      'auto'         => $auto,
    );
    $settings['uploadify_'. $field['field_name']] = $uploadify_post;
    drupal_add_js($settings, 'setting');
  }

//  $javascript = drupal_add_js(NULL, NULL);
//  var_dump($javascript['setting']);
//  var_dump($element);
  return $form;
}

/**
 * Implementation of hook_[widget type]_settings_uploadify_form().
 */
function uploadify_filefield_widget_settings_uploadify_form($widget) {
  $form = array();

  drupal_add_js(drupal_get_path('module', 'uploadify') .'/scripts/uploadify_settings.js');

  $form['uploadify'] = array(
    '#type' => 'fieldset',
    '#title' => t('Uploadify'),
    '#tree' => FALSE,
    '#weight' => -1,
  );
  $form['uploadify']['uploadify_enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable uploadify for this widget.'),
    '#default_value' => isset($widget['uploadify_enabled']) ? $widget['uploadify_enabled'] : 0,
  );
  $form['uploadify']['uploadify_auto'] = array(
    '#type' => 'checkbox',
    '#title' => t('Auto start upload.'),
    '#default_value' => isset($widget['uploadify_auto']) ? $widget['uploadify_auto'] : 0,
  );
  // TODO: settings about hiding things.

  $form['uploadify']['theme'] = array(
    '#type' => 'fieldset',
    '#title' => t('Theme settings'),
    '#tree' => FALSE,
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $options = array(
    'original' => t('Original (come with Uploadify library)'),
    'default'  => t('Default (come with this module)'),
    'custom'   => t('Custom (do not include any predefined theme package)'),
  );
  $form['uploadify']['theme']['uploadify_theme'] = array(
    '#type' => 'radios',
    '#title' => t('Theme package'),
    '#options' => $options,
    '#default_value' => isset($widget['uploadify_theme']) ? $widget['uploadify_theme'] : 'default',
    '#description' => t('If you choose custom, please make sure you have proper css loaded and image path set below.'),
  );
  $form['uploadify']['theme']['uploadify_cancel_img'] = array(
    '#type' => 'textfield',
    '#title' => t('Cancel image'),
    '#default_value' => isset($widget['uploadify_cancel_img']) ? $widget['uploadify_cancel_img'] : '',
    '#description' => t('This is the image appears on each upload item in the file queue. User click on the image to cancel a single upload.'),
  );
  $form['uploadify']['theme']['uploadify_hide_button'] = array(
    '#type' => 'checkbox',
    '#title' => t('Hide button'),
    '#default_value' => isset($widget['uploadify_hide_button']) ? $widget['uploadify_hide_button'] : 0,
    '#description' => t('This hides the flash "browse" button. You will need to theme it properly.'),
  );

  return $form;
}

/**
 * Implementation of hook_[widget type]_settings_uploadify_save().
 */
function uploadify_filefield_widget_settings_uploadify_save($widget) {
  return array('uploadify_enabled', 'uploadify_auto', 'uploadify_theme', 'uploadify_cancel_img', 'uploadify_hide_button');
}

/**
 * CCK Imagefield.
 */

/**
 * Implementation of hook_[widget type]_uploadify_js().
 */
function uploadify_imagefield_widget_uploadify_js($form, $form_build_id, $form_state) {
  return uploadify_filefield_widget_uploadify_js($form, $form_build_id, $form_state);
}

/**
 * Implementation of hook_[widget type]_uploadify_form_alter().
 */
function uploadify_imagefield_widget_uploadify_form_alter(&$form, $field) {
  return uploadify_filefield_widget_uploadify_form_alter($form, $field);
}

/**
 * Implementation of hook_[widget type]_uploadify_after_build().
 */
function uploadify_imagefield_widget_uploadify_after_build($form, $form_state, $field_name) {
  return uploadify_filefield_widget_uploadify_after_build($form, $form_state, $field_name);
}

/**
 * Implementation of hook_[widget type]_settings_uploadify_form().
 */
function uploadify_imagefield_widget_settings_uploadify_form($widget) {
  return uploadify_filefield_widget_settings_uploadify_form($widget);
}

/**
 * Implementation of hook_[widget type]_settings_uploadify_save().
 */
function uploadify_imagefield_widget_settings_uploadify_save($widget) {
  return uploadify_filefield_widget_settings_uploadify_save($widget);
}

/**
 * Themes.
 */

/**
 * FormAPI theme function.
 */
function theme_uploadify_widget($element) {
  return theme('form_element', $element, $element['#children']);
}

/**
 * Process variables for uploadify-element.tpl.php
 *
 * @see uploadify-element.tpl.php
 */
function template_preprocess_uploadify_element(&$variables) {
  $element = $variables['element'];

  // For CCK.
  if ($element['#type'] == 'uploadify_widget') {
    $field = content_fields($element['#field_name'], $element['#type_name']);
    // Force hide button.
    if (!in_array($field['widget']['uploadify_theme'], array('original', 'custom'))) {
      $field['widget']['uploadify_hide_button'] = TRUE;
    }

    $variables['field'] = $field;
    $variables['template_files'][] = 'uploadify-element-'. $field['type'];
    $variables['template_files'][] = 'uploadify-element-'. $field['widget']['type'];
    $variables['template_files'][] = 'uploadify-element-'. $field['field_name'];
  }
}

/**
 * Internal functions.
 */

/**
 * Get library.
 */
function uploadify_get_library($refresh = FALSE) {
  static $lib = array();

  // Get cache.
  $cid = 'uploadify_library';
  if (empty($lib) && empty($refresh)) {
    $data = cache_get($cid);
    if (!empty($data->data)) {
      $lib = $data->data;
    }
  }
  if (!empty($lib) && empty($refresh)) {
    return $lib;
  }

  $dirs = array();
  // Libraries module.
  if (module_exists('libraries')) {
    $dirs[] = libraries_get_path('uploadify');
  }
  // Libraries dir.
  $dirs[] = 'sites/all/libraries/uploadify';
  // Module dir.
  $dirs[] = drupal_get_path('module', 'uploadify') .'/uploadify';

  foreach ($dirs as $dir) {
    $files = file_scan_directory($dir, '^jquery\.uploadify\.v2\.1\.[0~4]\.min\.js$');
    if (!empty($files)) {
      $file = reset($files);
      if (!empty($file->filename)) {
        $lib['path'] = $dir;
        $lib['js'] = $file->filename;
        // TODO: version?
        // TODO: hook_requirements() ?
        break;
      }
    }
  }

  // Set cache.
  if (!empty($lib)) {
    cache_set($cid, $lib);
  }

  return $lib;
}

/**
 * Load uploadify JS.
 */
function uploadify_load_library($refresh = FALSE) {
  // TODO: static?

  $lib = uploadify_get_library($refresh);
  if (empty($lib['path'])) {
    return FALSE;
  }

  // TODO: http://drupal.org/project/swftools
  drupal_add_js($lib['path'] .'/swfobject.js');
  drupal_add_js($lib['js']);
  drupal_add_js(drupal_get_path('module', 'uploadify') .'/scripts/uploadify_default.js');

  return $lib;
}

/**
 * Build "scriptData".
 */
function uploadify_build_script_data($form, $extra_post = array()) {
  $script_data = new stdClass();
  $required = array('form_build_id', 'form_token', 'form_id');
  foreach ($required as $key) {
    if (empty($form[$key]['#value']) && empty($form[$key]['#default_value'])) {
      return FALSE;
    }
    $script_data->$key = empty($form[$key]['#value']) ? $form[$key]['#default_value'] : $form[$key]['#value'];
  }

  // Session
  $script_data->PHPSESSID = session_id();

  // TODO
  foreach ($extra_post as $key => $value) {
    $script_data->$key = $value;
  }

  return $script_data;
}

/**
 * Get an element from a form.
 *
 * @return array.
 *   The element or an empty array.
 */
function uploadify_get_form_element($form, $field_name, &$form_parents, &$tree_parents) {
  if (!is_array($form)) {
    return array();
  }

  if (array_key_exists($field_name, $form)) {
    $form_parents = array_merge($form_parents, array($field_name));
    $tree_parents = array_merge($tree_parents, array($field_name));
    return $form[$field_name];
  }

  foreach (element_children($form) as $key) {
    $cur_form_parents = array_merge($form_parents, array($key));
    if (!empty($form[$key]['#tree'])) {
      $cur_tree_parents = array_merge($tree_parents, array($key));
    }
    else {
      $cur_tree_parents = $tree_parents;
    }
    $element = uploadify_get_form_element($form[$key], $field_name, $cur_form_parents, $cur_tree_parents);
    if (!empty($element)) {
      $form_parents = $cur_form_parents;
      $tree_parents = $cur_tree_parents;
      return $element;
    }
  }

  return array();
}

/**
 * TODO: description.
 *
 * Doesn't support "file".
 */
function uploadify_get_post_id($form, $field_name, &$tree_parents = array()) {
  $return = array();
  $id = NULL;

  $form_parents = array();
  $element = uploadify_get_form_element($form, $field_name, $form_parents, $tree_parents);

  $want_types = array('select', 'radio', 'checkbox', 'textfield', 'value', 'hidden');
  if (in_array($element['#type'], $want_types) && isset($element['#id'])) {
    $return[$element['#id']] = $element['#id'];
  }

  foreach (element_children($element) as $key) {
    $cur_tree_parents = $tree_parents;
    $return += uploadify_get_post_id($element, $key, $cur_tree_parents);
  }

  return $return;
}

/**
 * TODO: description.
 *
 * XXX: obsolated by uploadify_get_post_id().
 *
 * Doesn't support "file".
 */
function uploadify_build_post_value($form, $field_name, $post = NULL, &$tree_parents = array()) {
  $return = array();
  $value = NULL;

  $form_parents = array();
  $element = uploadify_get_form_element($form, $field_name, $form_parents, $tree_parents);

  $want_types = array('select', 'radio', 'checkbox', 'textfield', 'value', 'hidden');
  if (in_array($element['#type'], $want_types)) {
    // If post, all post, otherwise discard post.
    if (isset($post)) {
      $value = $post[$field_name];
    }
    else {
      if (isset($element['#value'])) {
        $value = $element['#value'];
      }
      elseif (isset($element['#default_value'])) {
        $value = $element['#default_value'];
      }
    }
  }

  // Add in.
  if (isset($value) && !is_array($value) && !is_object($value)) {
    $key = $name = array_shift($tree_parents);
    if (!empty($tree_parents)) {
      $key .=  '['. implode('][', $tree_parents) .']';
    }
    $return[$key] = $value;
    array_unshift($tree_parents, $name);
  }

  foreach (element_children($element) as $key) {
    $cur_tree_parents = $tree_parents;
    $sub_post = isset($post[$field_name]) ? $post[$field_name] : $post;
    $return += uploadify_build_post_value($element, $key, $sub_post, $cur_tree_parents);
  }

  return $return;
}

/**
 * Get JS settings from Drupal JS settings.
 *
 * Only support settings using $element['#id'] as key.
 */
function uploadify_get_js_settings($form, $type, $keys) {
  $return = array();
  $javascript = drupal_add_js(NULL, NULL);
  if (isset($javascript['setting'])) {
    foreach ($keys as $key) {
      $form_parents = $tree_parents = array();
      $element = uploadify_get_form_element($form, $key, $form_parents, $tree_parents);
      if (isset($element['#id'])) {
        foreach ($javascript['setting'] as $settings) {
          if (isset($settings[$type][$element['#id']])) {
            $return[$element['#id']] = $settings[$type][$element['#id']];
          }
        }
      }
    }
  }
  return $return;
}

/**
 * Backported from Drupal 7.
 */

/**
 * Sets a value in a nested array with variable depth.
 *
 * @see Drupal 7 -> common.inc -> function drupal_array_set_nested_value().
 */
function uploadify_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) {
  $ref = &$array;
  foreach ($parents as $parent) {
    // PHP auto-creates container arrays and NULL entries without error if $ref
    // is NULL, but throws an error if $ref is set, but not an array.
    if ($force && isset($ref) && !is_array($ref)) {
      $ref = array();
    }
    $ref = &$ref[$parent];
  }
  $ref = $value;
}

/**
 * Retrieves a value from a nested array with variable depth.
 *
 * @see Drupal 7 -> common.inc -> function drupal_array_get_nested_value().
 */
function uploadify_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
  $ref = &$array;
  foreach ($parents as $parent) {
    if (is_array($ref) && array_key_exists($parent, $ref)) {
      $ref = &$ref[$parent];
    }
    else {
      $key_exists = FALSE;
      return NULL;
    }
  }
  $key_exists = TRUE;
  return $ref;
}

/**
 * Determines whether a nested array with variable depth contains all of the requested keys.
 *
 * @see Drupal 7 -> common.inc -> function drupal_array_nested_key_exists().
 */
function uploadify_array_nested_key_exists(array $array, array $parents) {
  // Although this function is similar to PHP's array_key_exists(), its
  // arguments should be consistent with drupal_array_get_nested_value().
  $key_exists = NULL;
  drupal_array_get_nested_value($array, $parents, $key_exists);
  return $key_exists;
}
