You are here

webform_multiple_file.webform.inc in Webform Multiple File 7

Provides features for webform multiple file upload component.

File

webform_multiple_file.webform.inc
View source
<?php

/**
 * @file
 * Provides features for webform multiple file upload component.
 */

/**
 * Describes multiple_file component.
 *
 * @see webform_component_invoke()
 */
function _webform_defaults_multiple_file() {
  return array(
    'name' => '',
    'form_key' => NULL,
    'required' => 0,
    'pid' => 0,
    'weight' => 0,
    'extra' => array(
      'filtering' => array(
        'types' => array(
          'gif',
          'jpg',
          'png',
        ),
        'addextensions' => '',
        'size' => '2 MB',
      ),
      'scheme' => 'public',
      'directory' => '',
      'rename' => '',
      'progress_indicator' => 'throbber',
      'title_display' => 0,
      'description' => '',
      'attributes' => array(),
      'private' => FALSE,
      'wrapper' => 'fieldset',
    ),
  );
}

/**
 * Sets theme function for multiple_file component.
 *
 * @see webform_component_invoke()
 */
function _webform_theme_multiple_file() {
  return array(
    'webform_display_multiple_files_set' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Settings form for multiple_file component.
 *
 * @see webform_component_invoke()
 */
function _webform_edit_multiple_file($component) {
  $form = array();
  $form['#element_validate'] = array(
    '_webform_edit_file_check_directory',
  );
  $form['#after_build'] = array(
    '_webform_edit_file_check_directory',
  );
  $form['validation']['size'] = array(
    '#type' => 'textfield',
    '#title' => t('Max upload size'),
    '#default_value' => $component['extra']['filtering']['size'],
    '#description' => t('Enter the max file size a user may upload such as 2 MB or 800 KB. Your server has a max upload size of @size.', array(
      '@size' => format_size(file_upload_max_size()),
    )),
    '#size' => 10,
    '#parents' => array(
      'extra',
      'filtering',
      'size',
    ),
    '#element_validate' => array(
      '_webform_edit_file_size_validate',
    ),
    '#weight' => 1,
  );
  $form['validation']['extensions'] = array(
    '#element_validate' => array(
      '_webform_edit_file_extensions_validate',
    ),
    '#parents' => array(
      'extra',
      'filtering',
    ),
    '#theme' => 'webform_edit_file_extensions',
    '#theme_wrappers' => array(
      'form_element',
    ),
    '#title' => t('Allowed file extensions'),
    '#attached' => array(
      'js' => array(
        drupal_get_path('module', 'webform') . '/js/webform-admin.js',
      ),
      'css' => array(
        drupal_get_path('module', 'webform') . '/css/webform-admin.css',
      ),
    ),
    '#weight' => 2,
  );

  // List of all currently valid extensions.
  $current_types = isset($component['extra']['filtering']['types']) ? $component['extra']['filtering']['types'] : array();
  $types = array(
    'gif',
    'jpg',
    'png',
  );
  $form['validation']['extensions']['types']['webimages'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Web images'),
    '#options' => drupal_map_assoc($types),
    '#default_value' => array_intersect($current_types, $types),
  );
  $types = array(
    'bmp',
    'eps',
    'tif',
    'pict',
    'psd',
  );
  $form['validation']['extensions']['types']['desktopimages'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Desktop images'),
    '#options' => drupal_map_assoc($types),
    '#default_value' => array_intersect($current_types, $types),
  );
  $types = array(
    'txt',
    'rtf',
    'html',
    'odf',
    'pdf',
    'doc',
    'docx',
    'ppt',
    'pptx',
    'xls',
    'xlsx',
    'xml',
  );
  $form['validation']['extensions']['types']['documents'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Documents'),
    '#options' => drupal_map_assoc($types),
    '#default_value' => array_intersect($current_types, $types),
  );
  $types = array(
    'avi',
    'mov',
    'mp3',
    'ogg',
    'wav',
  );
  $form['validation']['extensions']['types']['media'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Media'),
    '#options' => drupal_map_assoc($types),
    '#default_value' => array_intersect($current_types, $types),
  );
  $types = array(
    'bz2',
    'dmg',
    'gz',
    'jar',
    'rar',
    'sit',
    'tar',
    'zip',
  );
  $form['validation']['extensions']['types']['archives'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Archives'),
    '#options' => drupal_map_assoc($types),
    '#default_value' => array_intersect($current_types, $types),
  );
  $form['validation']['extensions']['addextensions'] = array(
    '#type' => 'textfield',
    '#title' => t('Additional extensions'),
    '#default_value' => $component['extra']['filtering']['addextensions'],
    '#description' => t('Enter a list of additional file extensions for this upload field, separated by commas.<br /> Entered extensions will be appended to checked items above.'),
    '#size' => 20,
    '#weight' => 3,
  );
  $scheme_options = array();
  foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
    $scheme_options[$scheme] = $stream_wrapper['name'];
  }
  $form['extra']['scheme'] = array(
    '#type' => 'radios',
    '#title' => t('Upload destination'),
    '#options' => $scheme_options,
    '#default_value' => $component['extra']['scheme'],
    '#description' => t('Private file storage has significantly more overhead than public files, but restricts file access to users who can view submissions.'),
    '#weight' => 4,
    '#access' => count($scheme_options) > 1,
  );
  $form['extra']['directory'] = array(
    '#type' => 'textfield',
    '#title' => t('Upload directory'),
    '#default_value' => $component['extra']['directory'],
    '#description' => t('You may optionally specify a sub-directory to store your files.') . ' ' . theme('webform_token_help'),
    '#weight' => 5,
    '#field_prefix' => 'webform/',
  );
  $form['extra']['rename'] = array(
    '#type' => 'textfield',
    '#title' => t('Rename files'),
    '#default_value' => $component['extra']['rename'],
    '#description' => t('You may optionally use tokens to create a pattern used to rename files upon submission. Omit the extension; it will be added automatically.') . ' ' . theme('webform_token_help', array(
      'groups' => array(
        'node',
        'submission',
      ),
    )),
    '#weight' => 6,
    '#element_validate' => array(
      '_webform_edit_multiple_file_rename_validate',
    ),
    '#access' => webform_variable_get('webform_token_access'),
  );
  $options = array(
    WEBFORM_MULTIPLE_FILE_CARDINALITY_UNLIMITED => t('Unlimited'),
  ) + drupal_map_assoc(array(
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
  ));
  $form['extra']['cardinality'] = array(
    '#type' => 'select',
    '#title' => t('Number of values'),
    '#options' => $options,
    '#default_value' => isset($component['extra']['cardinality']) ? $component['extra']['cardinality'] : WEBFORM_MULTIPLE_FILE_CARDINALITY_UNLIMITED,
    '#weight' => 5,
  );
  $form['display']['progress_indicator'] = array(
    '#type' => 'radios',
    '#title' => t('Progress indicator'),
    '#options' => array(
      'throbber' => t('Throbber'),
      'bar' => t('Bar with progress meter'),
    ),
    '#default_value' => $component['extra']['progress_indicator'],
    '#description' => t('The throbber display does not show the status of uploads but takes up less space. The progress bar is helpful for monitoring progress on large uploads.'),
    '#weight' => 16,
    '#access' => file_progress_implementation(),
    '#parents' => array(
      'extra',
      'progress_indicator',
    ),
  );
  $form['display']['wrapper'] = array(
    '#type' => 'select',
    '#title' => t('Wrapper'),
    '#options' => array(
      'fieldset' => t('Fieldset (with heading)'),
      'container' => t('Container (without heading)'),
    ),
    '#default_value' => isset($component['extra']['wrapper']) ? $component['extra']['wrapper'] : 'fieldset',
    '#description' => t('Form item wrapper type.'),
    '#parents' => array(
      'extra',
      'wrapper',
    ),
    '#weight' => 20,
  );
  return $form;
}

/**
 * Form validation callback.
 *
 * A Form API element validate function to ensure that the rename string is
 * either empty or contains at least one token.
 */
function _webform_edit_multiple_file_rename_validate($element, &$form_state, $form) {
  $rename = trim($form_state['values']['extra']['rename']);
  form_set_value($element, $rename, $form_state);
  if (strlen($rename) && !count(token_scan($rename))) {
    form_error($element, t('To create unique file names, use at least one token in the file name pattern.'));
  }
}

/**
 * Render the multiple_file component.
 *
 * Works similar to file field widget.
 *
 * @see webform_component_invoke()
 */
function _webform_render_multiple_file($component, $value = NULL, $filter = TRUE) {
  $node = isset($component['nid']) ? node_load($component['nid']) : NULL;

  // Cap the upload size according to the PHP limit.
  $max_filesize = parse_size(file_upload_max_size());
  $set_filesize = $component['extra']['filtering']['size'];
  if (!empty($set_filesize) && parse_size($set_filesize) < $max_filesize) {
    $max_filesize = parse_size($set_filesize);
  }
  $element_info = element_info('managed_file');
  $element = array(
    '#type' => 'managed_file',
    '#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
    '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
    '#required' => empty($value) ? $component['required'] : FALSE,
    '#default_value' => isset($value[0]) ? $value[0] : NULL,
    '#attributes' => $component['extra']['attributes'],
    '#upload_validators' => array(
      'file_validate_size' => array(
        $max_filesize,
      ),
      'file_validate_extensions' => array(
        implode(' ', $component['extra']['filtering']['types']),
      ),
    ),
    '#pre_render' => array_merge(element_info_property('managed_file', '#pre_render'), array(
      'webform_file_allow_access',
    )),
    '#upload_location' => $component['extra']['scheme'] . '://webform/' . ($filter ? drupal_strtolower(webform_replace_tokens($component['extra']['directory'], $node)) : $component['extra']['directory']),
    '#progress_indicator' => $component['extra']['progress_indicator'],
    '#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
    '#weight' => $component['weight'],
    '#process' => array_merge($element_info['#process'], array(
      'webform_multiple_file_process_element',
    )),
    '#theme_wrappers' => array(
      'webform_element',
    ),
    '#webform_component' => $component,
    '#webform_multiple_file_form_key' => array(
      '#type' => 'value',
      '#value' => $component['form_key'],
    ),
  );
  if ($component['extra']['cardinality'] == 1) {

    // Set the default value.
    if (!empty($value)) {
      if (is_array($value[0])) {
        $fid = $value[0]['fid'];
      }
      else {
        $fid = $value[0];
      }
    }
    $element['#default_value'] = !empty($fid) ? $fid : NULL;

    // If there's only one field, return it as delta 0.
    if (empty($element['#default_value'])) {
      $element['#description'] = theme('file_upload_help', array(
        'description' => $element['#description'],
        'upload_validators' => $element['#upload_validators'],
      ));
    }
    $elements = array(
      $element,
    );
  }
  else {
    $delta = 0;
    if (!empty($value)) {
      foreach ($value as $item) {
        if (is_array($item)) {
          $fid = $item['fid'];
        }
        else {
          $fid = $item;
        }
        $elements[$delta] = $element;
        $elements[$delta]['#default_value'] = !empty($fid) ? $fid : NULL;
        $elements[$delta]['#weight'] = $delta;
        $delta++;
      }
    }

    // Adds one more empty row for new file uploads.
    if ($component['extra']['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta < $component['extra']['cardinality']) {
      $elements[$delta] = $element;
      $elements[$delta]['#default_value'] = array();
      $elements[$delta]['#weight'] = $delta;
    }

    // Extra functionality for the group of elements.
    $elements['#file_upload_delta'] = $delta;
    $elements['#theme'] = 'file_widget_multiple';
    $elements['#theme_wrappers'] = array(
      isset($component['extra']['wrapper']) ? $component['extra']['wrapper'] : 'fieldset',
    );
    $elements['#process'] = array(
      'file_field_widget_process_multiple',
    );
    $elements['#title'] = $element['#title'];
    $elements['#title_display'] = 'invisible';
    $elements['#display_field'] = 0;
    $elements['#description'] = $element['#description'];
    $elements['#weight'] = $component['weight'];
    $elements['#attributes']['class'][] = 'webform-component--' . $component['form_key'];

    // Add some properties that will eventually be added to the
    // file upload field.
    $elements['#file_upload_title'] = t('Add a new file');
    $elements['#file_upload_description'] = theme('file_upload_help', array(
      'description' => '',
      'upload_validators' => $elements[0]['#upload_validators'],
    ));
    $elements['#translatable'] = array(
      'title',
      'description',
    );
  }
  return $elements;
}

/**
 * Submit value of multiple_file component.
 *
 * @see webform_component_invoke()
 */
function _webform_submit_multiple_file($component, $value) {
  return isset($value) ? array_filter($value) : NULL;
}

/**
 * Displays uploaded files through multiple_file component.
 *
 * @see webform_component_invoke()
 */
function _webform_display_multiple_file($component, $value, $format = 'html') {
  if (!empty($value)) {
    $files = array();
    foreach ($value as $key => $fid) {
      $files[] = array(
        '#value' => $fid ? webform_get_file($fid) : NULL,
        '#theme' => 'webform_display_file',
        '#theme_wrappers' => $format == 'text' ? array(
          'webform_element_text',
        ) : array(
          'webform_display_multiple_files_set',
        ),
        '#format' => $format,
        '#parents' => array(
          $key,
        ),
        '#webform_component' => $component,
      );
    }
    $files['#translatable'] = array(
      'title',
    );
    $files['#weight'] = $component['weight'];
    $files['#title'] = $component['name'];
    $files['#theme_wrappers'] = $format == 'text' ? array(
      'webform_element_text',
    ) : array(
      'webform_element',
    );
  }
  else {
    $files = array();
    $files[] = array(
      '#value' => NULL,
      '#theme' => 'webform_display_file',
      '#theme_wrappers' => $format == 'text' ? array(
        'webform_element_text',
      ) : array(
        'webform_display_multiple_files_set',
      ),
      '#format' => $format,
      '#parents' => NULL,
      '#webform_component' => $component,
    );
    $files['#translatable'] = array(
      'title',
    );
    $files['#weight'] = $component['weight'];
    $files['#title'] = $component['name'];
    $files['#theme_wrappers'] = $format == 'text' ? array(
      'webform_element_text',
    ) : array(
      'webform_element',
    );
  }
  return !empty($files) ? $files : NULL;
}

/**
 * Describes delete process of multiple_file component.
 *
 * @see webform_component_invoke()
 */
function _webform_delete_multiple_file($component, $value) {
  if (!empty($value)) {
    foreach ($value as $fid) {
      if (!empty($fid) && ($file = webform_get_file($fid))) {
        file_usage_delete($file, 'webform');
        file_delete($file);
      }
    }
  }
}

/**
 * Attaches multiple_file component data to mail.
 *
 * @see webform_component_invoke()
 */
function _webform_attachments_multiple_file($component, $value) {
  foreach ($value as $fid) {
    $file = (array) webform_get_file($fid);
    if (!empty($file)) {

      // This is necessary until the next release of mimemail is out,
      // see [#1388786].
      $file['filepath'] = $file['uri'];
      $files[] = $file;
    }
  }
  return !empty($files) ? $files : NULL;
}

/**
 * Processing multiple_file data for analysis page.
 *
 * @see webform_component_invoke()
 */
function _webform_analysis_multiple_file($component, $sids = array()) {

  // Get submissions with uploaded multifiles.
  $query = db_select('webform_submitted_data', 'wsd', array(
    'fetch' => PDO::FETCH_ASSOC,
  ))
    ->fields('wsd', array(
    'no',
    'data',
    'sid',
  ))
    ->condition('nid', $component['nid'])
    ->condition('cid', $component['cid']);
  if (count($sids)) {
    $query
      ->condition('sid', $sids, 'IN');
  }
  $files_count = 0;
  $sizetotal = 0;
  $submissions_with_files = array();
  $result = $query
    ->execute();
  foreach ($result as $data) {
    $file = webform_get_file($data['data']);
    if (isset($file->filesize)) {
      $files_count++;
      $sizetotal += $file->filesize;
    }
    if (!in_array($data['sid'], $submissions_with_files)) {
      $submissions_with_files[] = $data['sid'];
    }
  }

  // Get count of submissions of current webform.
  $total = db_select('webform_submitted_data', 'wsd', array(
    'fetch' => PDO::FETCH_ASSOC,
  ))
    ->fields('wsd', array(
    'sid',
  ))
    ->condition('nid', $component['nid'])
    ->groupBy('sid')
    ->countQuery()
    ->execute()
    ->fetchField();
  $rows[0] = array(
    t('Left Blank'),
    $total - count($submissions_with_files),
  );
  $rows[1] = array(
    t('User uploaded files'),
    $files_count,
  );
  $other[0] = array(
    t('Average uploaded file size'),
    $sizetotal != 0 ? (int) ($sizetotal / $files_count / 1024) . ' KB' : '0',
  );
  return array(
    'table_rows' => $rows,
    'other_data' => $other,
  );
}

/**
 * Provides multiple_file data for results webform table.
 *
 * @see webform_component_invoke()
 */
function _webform_table_multiple_file($component, $value) {
  $output = '';
  if (!empty($value)) {
    foreach ($value as $fid) {
      $file = webform_get_file($fid);
      if (!empty($file->fid)) {
        $output .= l(webform_file_name($file->uri), webform_file_url($file->uri));
        $output .= ' (' . (int) ($file->filesize / 1024) . ' KB)';
        $output .= "<br />\n";
      }
    }
  }
  return $output;
}

/**
 * Configuration of CSV headers for multiple_file data export.
 *
 * @see webform_component_invoke()
 */
function _webform_csv_headers_multiple_file($component, $export_options) {
  $header = array();

  // Two columns in header.
  $header[0] = array(
    '',
    '',
  );
  $header[1] = array(
    $component['name'],
    '',
  );
  $header[2] = array(
    t('Name'),
    t('Filesize (KB)'),
  );
  return $header;
}

/**
 * Processing multiple_file data for CSV export.
 *
 * @see webform_component_invoke()
 */
function _webform_csv_data_multiple_file($component, $export_options, $value) {
  $files_size = 0;
  $file_urls = array();
  if (!empty($value)) {
    foreach ($value as $fid) {
      $file = webform_get_file($fid);
      if (!empty($file->filename)) {
        $file_urls[] = webform_file_url($file->uri);
        $files_size += $file->filesize / 1024;
      }
    }
  }
  return empty($files_size) ? array(
    '',
    '',
  ) : array(
    implode("|", $file_urls),
    (int) $files_size,
  );
}

/**
 * Process form element callback.
 *
 * Adds changes to managed_file element structure.
 *
 * @see file_field_widget_process()
 */
function webform_multiple_file_process_element($element, &$form_state, $form) {
  $element['#theme'] = 'file_widget';

  // Ajax settings to upload and remove of any individual file
  // and update group of already uploaded files.
  $parents = array_slice($element['#array_parents'], 0, -1);
  $cardinality = drupal_array_get_nested_value($form, array_merge($parents, array(
    '#webform_component',
    'extra',
    'cardinality',
  )));
  if ($cardinality != 1) {
    $new_path = 'file/ajax/' . implode('/', $parents) . '/' . $form['form_build_id']['#value'];
    $component_element = drupal_array_get_nested_value($form, $parents);
    $new_wrapper = $component_element['#id'] . '-ajax-wrapper';
    foreach (element_children($element) as $key) {
      if (isset($element[$key]['#ajax'])) {
        $element[$key]['#ajax']['path'] = $new_path;
        $element[$key]['#ajax']['wrapper'] = $new_wrapper;
      }
    }
    unset($element['#prefix'], $element['#suffix']);
  }

  // Add another submit handler to the upload and remove buttons,
  // to implement functionality needed by the webform component.
  foreach (array(
    'upload_button',
    'remove_button',
  ) as $key) {
    $element[$key]['#submit'][] = 'webform_multiple_file_managed_file_submit';
    $limit_validation_errors = array(
      array_slice($element['#parents'], 0, -1),
    );
    $element[$key]['#limit_validation_errors'] = $limit_validation_errors;
  }
  return $element;
}

/**
 * Form submission handler.
 *
 * Form submission handler for upload/remove button of managed_file
 * in multiple_file webform component.
 * This runs in addition to and after file_managed_file_submit().
 *
 * @see file_managed_file_submit()
 * @see webform_multiple_file_process_element()
 * @see file_field_widget_submit()
 */
function webform_multiple_file_managed_file_submit($form, &$form_state) {

  // During the form rebuild, _webform_render_multiple_file() will create
  // file elements using re-indexed deltas, so clear out
  // $form_state['input'] to avoid a mismatch between old and new deltas.
  // The rebuilt elements will have #default_value set appropriately
  // for the current state of the field, so nothing is lost in doing this.
  $button = $form_state['triggering_element'];
  $parents = array_slice($button['#parents'], 0, -2);

  // Reset input values.
  drupal_array_set_nested_value($form_state['input'], $parents, NULL);

  // Webform component info.
  $component = drupal_array_get_nested_value($form, array_merge($parents, array(
    '#webform_component',
  )));
  $form_key = $component['form_key'];

  // All submitted values.
  $submitted_values = drupal_array_get_nested_value($form_state['values'], array_slice($button['#array_parents'], 0, -3));

  // Processes values only for multiple files component.
  $component_values = isset($submitted_values[$form_key]) ? $submitted_values[$form_key] : array();
  foreach ($component_values as $delta => $submitted_value) {
    if (empty($submitted_value) || is_array($submitted_value) && empty($submitted_value['fid'])) {
      unset($component_values[$delta]);
    }
  }

  // Re-index deltas after removing empty items.
  $submitted_values[$form_key] = array_values($component_values);

  // Retrieve parent ID for Webform component.
  $pid = $component['pid'];

  // Changes key from 'form_key' to 'cid' of webform component.
  $flatten_values = _webform_client_form_submit_flatten($form['#node'], $submitted_values, $pid);

  // Key 'submitted' is used in Webform module as constant all over the code.
  // Seems that is correct to use this key.
  $parents = array(
    'submitted',
  );

  // Update form_state values.
  drupal_array_set_nested_value($form_state['values'], $parents, $flatten_values);

  // Adds values to $form_state['storage'] to prevent missing values during another element AJAX-submit.
  $form_state['storage'] = isset($form_state['storage']) ? $form_state['storage'] : array();
  $storage_values = drupal_array_get_nested_value($form_state['storage'], $parents);
  $storage_values = isset($storage_values) ? $storage_values : array();
  foreach ($flatten_values as $cid => $value) {
    $storage_values[$cid] = $value;
  }
  drupal_array_set_nested_value($form_state['storage'], $parents, $storage_values);
}

/**
 * Theme function for multiple file wrapping.
 */
function theme_webform_display_multiple_files_set($variables) {
  $output = '<div class="webform-multiple-file">' . $variables['element']['#children'] . '</div>';
  return $output;
}

Functions

Namesort descending Description
theme_webform_display_multiple_files_set Theme function for multiple file wrapping.
webform_multiple_file_managed_file_submit Form submission handler.
webform_multiple_file_process_element Process form element callback.
_webform_analysis_multiple_file Processing multiple_file data for analysis page.
_webform_attachments_multiple_file Attaches multiple_file component data to mail.
_webform_csv_data_multiple_file Processing multiple_file data for CSV export.
_webform_csv_headers_multiple_file Configuration of CSV headers for multiple_file data export.
_webform_defaults_multiple_file Describes multiple_file component.
_webform_delete_multiple_file Describes delete process of multiple_file component.
_webform_display_multiple_file Displays uploaded files through multiple_file component.
_webform_edit_multiple_file Settings form for multiple_file component.
_webform_edit_multiple_file_rename_validate Form validation callback.
_webform_render_multiple_file Render the multiple_file component.
_webform_submit_multiple_file Submit value of multiple_file component.
_webform_table_multiple_file Provides multiple_file data for results webform table.
_webform_theme_multiple_file Sets theme function for multiple_file component.