Drupal PHP Cross Reference Content Management Systems

Source: /modules/file/file.field.inc - 1008 lines - 35097 bytes - Summary - Text - Print

   1  <?php
   2  
   3  /**
   4   * @file
   5   * Field module functionality for the File module.
   6   */
   7  
   8  /**
   9   * Implements hook_field_info().
  10   */
  11  function file_field_info() {
  12    return array(
  13      'file' => array(
  14        'label' => t('File'),
  15        'description' => t('This field stores the ID of a file as an integer value.'),
  16        'settings' => array(
  17          'display_field' => 0,
  18          'display_default' => 0,
  19          'uri_scheme' => variable_get('file_default_scheme', 'public'),
  20        ),
  21        'instance_settings' => array(
  22          'file_extensions' => 'txt',
  23          'file_directory' => '',
  24          'max_filesize' => '',
  25          'description_field' => 0,
  26        ),
  27        'default_widget' => 'file_generic',
  28        'default_formatter' => 'file_default',
  29      ),
  30    );
  31  }
  32  
  33  /**
  34   * Implements hook_field_settings_form().
  35   */
  36  function file_field_settings_form($field, $instance, $has_data) {
  37    $defaults = field_info_field_settings($field['type']);
  38    $settings = array_merge($defaults, $field['settings']);
  39  
  40    $form['#attached']['js'][] = drupal_get_path('module', 'file') . '/file.js';
  41  
  42    $form['display_field'] = array(
  43      '#type' => 'checkbox',
  44      '#title' => t('Enable <em>Display</em> field'),
  45      '#default_value' => $settings['display_field'],
  46      '#description' => t('The display option allows users to choose if a file should be shown when viewing the content.'),
  47    );
  48    $form['display_default'] = array(
  49      '#type' => 'checkbox',
  50      '#title' => t('Files displayed by default'),
  51      '#default_value' => $settings['display_default'],
  52      '#description' => t('This setting only has an effect if the display option is enabled.'),
  53    );
  54  
  55    $scheme_options = array();
  56    foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
  57      $scheme_options[$scheme] = $stream_wrapper['name'];
  58    }
  59    $form['uri_scheme'] = array(
  60      '#type' => 'radios',
  61      '#title' => t('Upload destination'),
  62      '#options' => $scheme_options,
  63      '#default_value' => $settings['uri_scheme'],
  64      '#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
  65      '#disabled' => $has_data,
  66    );
  67  
  68    return $form;
  69  }
  70  
  71  /**
  72   * Implements hook_field_instance_settings_form().
  73   */
  74  function file_field_instance_settings_form($field, $instance) {
  75    $settings = $instance['settings'];
  76  
  77    $form['file_directory'] = array(
  78      '#type' => 'textfield',
  79      '#title' => t('File directory'),
  80      '#default_value' => $settings['file_directory'],
  81      '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.'),
  82      '#element_validate' => array('_file_generic_settings_file_directory_validate'),
  83      '#weight' => 3,
  84    );
  85  
  86    // Make the extension list a little more human-friendly by comma-separation.
  87    $extensions = str_replace(' ', ', ', $settings['file_extensions']);
  88    $form['file_extensions'] = array(
  89      '#type' => 'textfield',
  90      '#title' => t('Allowed file extensions'),
  91      '#default_value' => $extensions,
  92      '#description' => t('Separate extensions with a space or comma and do not include the leading dot.'),
  93      '#element_validate' => array('_file_generic_settings_extensions'),
  94      '#weight' => 1,
  95      // By making this field required, we prevent a potential security issue
  96      // that would allow files of any type to be uploaded.
  97      '#required' => TRUE,
  98    );
  99  
 100    $form['max_filesize'] = array(
 101      '#type' => 'textfield',
 102      '#title' => t('Maximum upload size'),
 103      '#default_value' => $settings['max_filesize'],
 104      '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', array('%limit' => format_size(file_upload_max_size()))),
 105      '#size' => 10,
 106      '#element_validate' => array('_file_generic_settings_max_filesize'),
 107      '#weight' => 5,
 108    );
 109  
 110    $form['description_field'] = array(
 111      '#type' => 'checkbox',
 112      '#title' => t('Enable <em>Description</em> field'),
 113      '#default_value' => isset($settings['description_field']) ? $settings['description_field'] : '',
 114      '#description' => t('The description field allows users to enter a description about the uploaded file.'),
 115      '#parents' => array('instance', 'settings', 'description_field'),
 116      '#weight' => 11,
 117    );
 118  
 119    return $form;
 120  }
 121  
 122  /**
 123   * Element validate callback for the maximum upload size field.
 124   *
 125   * Ensure a size that can be parsed by parse_size() has been entered.
 126   */
 127  function _file_generic_settings_max_filesize($element, &$form_state) {
 128    if (!empty($element['#value']) && !is_numeric(parse_size($element['#value']))) {
 129      form_error($element, t('The "!name" option must contain a valid value. You may either leave the text field empty or enter a string like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes).', array('!name' => t($element['title']))));
 130    }
 131  }
 132  
 133  /**
 134   * Element validate callback for the allowed file extensions field.
 135   *
 136   * This doubles as a convenience clean-up function and a validation routine.
 137   * Commas are allowed by the end-user, but ultimately the value will be stored
 138   * as a space-separated list for compatibility with file_validate_extensions().
 139   */
 140  function _file_generic_settings_extensions($element, &$form_state) {
 141    if (!empty($element['#value'])) {
 142      $extensions = preg_replace('/([, ]+\.?)/', ' ', trim(strtolower($element['#value'])));
 143      $extensions = array_filter(explode(' ', $extensions));
 144      $extensions = implode(' ', array_unique($extensions));
 145      if (!preg_match('/^([a-z0-9]+([.][a-z0-9])* ?)+$/', $extensions)) {
 146        form_error($element, t('The list of allowed extensions is not valid, be sure to exclude leading dots and to separate extensions with a comma or space.'));
 147      }
 148      else {
 149        form_set_value($element, $extensions, $form_state);
 150      }
 151    }
 152  }
 153  
 154  /**
 155   * Element validate callback for the file destination field.
 156   *
 157   * Remove slashes from the beginning and end of the destination value and ensure
 158   * that the file directory path is not included at the beginning of the value.
 159   */
 160  function _file_generic_settings_file_directory_validate($element, &$form_state) {
 161    // Strip slashes from the beginning and end of $widget['file_directory'].
 162    $value = trim($element['#value'], '\\/');
 163    form_set_value($element, $value, $form_state);
 164  }
 165  
 166  /**
 167   * Implements hook_field_load().
 168   */
 169  function file_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
 170  
 171    $fids = array();
 172    foreach ($entities as $id => $entity) {
 173      // Load the files from the files table.
 174      foreach ($items[$id] as $delta => $item) {
 175        if (!empty($item['fid'])) {
 176          $fids[] = $item['fid'];
 177        }
 178      }
 179    }
 180    $files = file_load_multiple($fids);
 181  
 182    foreach ($entities as $id => $entity) {
 183      foreach ($items[$id] as $delta => $item) {
 184        // If the file does not exist, mark the entire item as empty.
 185        if (empty($item['fid']) || !isset($files[$item['fid']])) {
 186          $items[$id][$delta] = NULL;
 187        }
 188        else {
 189          $items[$id][$delta] = array_merge($item, (array) $files[$item['fid']]);
 190        }
 191      }
 192    }
 193  }
 194  
 195  /**
 196   * Implements hook_field_prepare_view().
 197   */
 198  function file_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
 199    // Remove files specified to not be displayed.
 200    foreach ($entities as $id => $entity) {
 201      foreach ($items[$id] as $delta => $item) {
 202        if (!file_field_displayed($item, $field)) {
 203          unset($items[$id][$delta]);
 204        }
 205      }
 206      // Ensure consecutive deltas.
 207      $items[$id] = array_values($items[$id]);
 208    }
 209  }
 210  
 211  /**
 212   * Implements hook_field_presave().
 213   */
 214  function file_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
 215    // Make sure that each file which will be saved with this object has a
 216    // permanent status, so that it will not be removed when temporary files are
 217    // cleaned up.
 218    foreach ($items as $item) {
 219      $file = file_load($item['fid']);
 220      if (!$file->status) {
 221        $file->status = FILE_STATUS_PERMANENT;
 222        file_save($file);
 223      }
 224    }
 225  }
 226  
 227  /**
 228   * Implements hook_field_insert().
 229   */
 230  function file_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
 231    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
 232  
 233    // Add a new usage of each uploaded file.
 234    foreach ($items as $item) {
 235      $file = (object) $item;
 236      file_usage_add($file, 'file', $entity_type, $id);
 237    }
 238  }
 239  
 240  /**
 241   * Implements hook_field_update().
 242   *
 243   * Checks for files that have been removed from the object.
 244   */
 245  function file_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
 246    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
 247  
 248    // On new revisions, all files are considered to be a new usage and no
 249    // deletion of previous file usages are necessary.
 250    if (!empty($entity->revision)) {
 251      foreach ($items as $item) {
 252        $file = (object) $item;
 253        file_usage_add($file, 'file', $entity_type, $id);
 254      }
 255      return;
 256    }
 257  
 258    // Build a display of the current FIDs.
 259    $current_fids = array();
 260    foreach ($items as $item) {
 261      $current_fids[] = $item['fid'];
 262    }
 263  
 264    // Compare the original field values with the ones that are being saved.
 265    $original = $entity->original;
 266    $original_fids = array();
 267    if (!empty($original->{$field['field_name']}[$langcode])) {
 268      foreach ($original->{$field['field_name']}[$langcode] as $original_item) {
 269        $original_fids[] = $original_item['fid'];
 270        if (isset($original_item['fid']) && !in_array($original_item['fid'], $current_fids)) {
 271          // Decrement the file usage count by 1 and delete the file if possible.
 272          file_field_delete_file($original_item, $field, $entity_type, $id);
 273        }
 274      }
 275    }
 276  
 277    // Add new usage entries for newly added files.
 278    foreach ($items as $item) {
 279      if (!in_array($item['fid'], $original_fids)) {
 280        $file = (object) $item;
 281        file_usage_add($file, 'file', $entity_type, $id);
 282      }
 283    }
 284  }
 285  
 286  /**
 287   * Implements hook_field_delete().
 288   */
 289  function file_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
 290    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
 291  
 292    // Delete all file usages within this entity.
 293    foreach ($items as $delta => $item) {
 294      file_field_delete_file($item, $field, $entity_type, $id, 0);
 295    }
 296  }
 297  
 298  /**
 299   * Implements hook_field_delete_revision().
 300   */
 301  function file_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) {
 302    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
 303    foreach ($items as $delta => $item) {
 304      // Decrement the file usage count by 1 and delete the file if possible.
 305      if (file_field_delete_file($item, $field, $entity_type, $id)) {
 306        $items[$delta] = NULL;
 307      }
 308    }
 309  }
 310  
 311  /**
 312   * Decrements the usage count for a file and attempts to delete it.
 313   *
 314   * This function only has an effect if the file being deleted is used only by
 315   * File module.
 316   *
 317   * @param $item
 318   *   The field item that contains a file array.
 319   * @param $field
 320   *   The field structure for the operation.
 321   * @param $entity_type
 322   *   The type of $entity.
 323   * @param $id
 324   *   The entity ID which contains the file being deleted.
 325   * @param $count
 326   *   (optional) The number of references to decrement from the object
 327   *   containing the file. Defaults to 1.
 328   *
 329   * @return
 330   *   Boolean TRUE if the file was deleted, or an array of remaining references
 331   *   if the file is still in use by other modules. Boolean FALSE if an error
 332   *   was encountered.
 333   */
 334  function file_field_delete_file($item, $field, $entity_type, $id, $count = 1) {
 335    // To prevent the file field from deleting files it doesn't know about, check
 336    // the file reference count. Temporary files can be deleted because they
 337    // are not yet associated with any content at all.
 338    $file = (object) $item;
 339    $file_usage = file_usage_list($file);
 340    if ($file->status == 0 || !empty($file_usage['file'])) {
 341      file_usage_delete($file, 'file', $entity_type, $id, $count);
 342      return file_delete($file);
 343    }
 344  
 345    // Even if the file is not deleted, return TRUE to indicate the file field
 346    // record can be removed from the field database tables.
 347    return TRUE;
 348  }
 349  
 350  /**
 351   * Implements hook_field_is_empty().
 352   */
 353  function file_field_is_empty($item, $field) {
 354    return empty($item['fid']);
 355  }
 356  
 357  /**
 358   * Determines whether a file should be displayed when outputting field content.
 359   *
 360   * @param $item
 361   *   A field item array.
 362   * @param $field
 363   *   A field array.
 364   *
 365   * @return
 366   *   Boolean TRUE if the file will be displayed, FALSE if the file is hidden.
 367   */
 368  function file_field_displayed($item, $field) {
 369    if (!empty($field['settings']['display_field'])) {
 370      return (bool) $item['display'];
 371    }
 372    return TRUE;
 373  }
 374  
 375  /**
 376   * Implements hook_field_formatter_info().
 377   */
 378  function file_field_formatter_info() {
 379    return array(
 380      'file_default' => array(
 381        'label' => t('Generic file'),
 382        'field types' => array('file'),
 383      ),
 384      'file_table' => array(
 385        'label' => t('Table of files'),
 386        'field types' => array('file'),
 387      ),
 388      'file_url_plain' => array(
 389        'label' => t('URL to file'),
 390        'field types' => array('file'),
 391      ),
 392    );
 393  }
 394  
 395  /**
 396   * Implements hook_field_widget_info().
 397   */
 398  function file_field_widget_info() {
 399    return array(
 400      'file_generic' => array(
 401        'label' => t('File'),
 402        'field types' => array('file'),
 403        'settings' => array(
 404          'progress_indicator' => 'throbber',
 405        ),
 406        'behaviors' => array(
 407          'multiple values' => FIELD_BEHAVIOR_CUSTOM,
 408          'default value' => FIELD_BEHAVIOR_NONE,
 409        ),
 410      ),
 411    );
 412  }
 413  
 414  /**
 415   * Implements hook_field_widget_settings_form().
 416   */
 417  function file_field_widget_settings_form($field, $instance) {
 418    $widget = $instance['widget'];
 419    $settings = $widget['settings'];
 420  
 421    $form['progress_indicator'] = array(
 422      '#type' => 'radios',
 423      '#title' => t('Progress indicator'),
 424      '#options' => array(
 425        'throbber' => t('Throbber'),
 426        'bar' => t('Bar with progress meter'),
 427      ),
 428      '#default_value' => $settings['progress_indicator'],
 429      '#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.'),
 430      '#weight' => 16,
 431      '#access' => file_progress_implementation(),
 432    );
 433  
 434    return $form;
 435  }
 436  
 437  /**
 438   * Implements hook_field_widget_form().
 439   */
 440  function file_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
 441  
 442    $defaults = array(
 443      'fid' => 0,
 444      'display' => !empty($field['settings']['display_default']),
 445      'description' => '',
 446    );
 447  
 448    // Load the items for form rebuilds from the field state as they might not be
 449    // in $form_state['values'] because of validation limitations. Also, they are
 450    // only passed in as $items when editing existing entities.
 451    $field_state = field_form_get_state($element['#field_parents'], $field['field_name'], $langcode, $form_state);
 452    if (isset($field_state['items'])) {
 453      $items = $field_state['items'];
 454    }
 455  
 456    // Essentially we use the managed_file type, extended with some enhancements.
 457    $element_info = element_info('managed_file');
 458    $element += array(
 459      '#type' => 'managed_file',
 460      '#upload_location' => file_field_widget_uri($field, $instance),
 461      '#upload_validators' => file_field_widget_upload_validators($field, $instance),
 462      '#value_callback' => 'file_field_widget_value',
 463      '#process' => array_merge($element_info['#process'], array('file_field_widget_process')),
 464      '#progress_indicator' => $instance['widget']['settings']['progress_indicator'],
 465      // Allows this field to return an array instead of a single value.
 466      '#extended' => TRUE,
 467    );
 468  
 469    if ($field['cardinality'] == 1) {
 470      // Set the default value.
 471      $element['#default_value'] = !empty($items) ? $items[0] : $defaults;
 472      // If there's only one field, return it as delta 0.
 473      if (empty($element['#default_value']['fid'])) {
 474        $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
 475      }
 476      $elements = array($element);
 477    }
 478    else {
 479      // If there are multiple values, add an element for each existing one.
 480      foreach ($items as $item) {
 481        $elements[$delta] = $element;
 482        $elements[$delta]['#default_value'] = $item;
 483        $elements[$delta]['#weight'] = $delta;
 484        $delta++;
 485      }
 486      // And then add one more empty row for new uploads except when this is a
 487      // programmed form as it is not necessary.
 488      if (($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta < $field['cardinality']) && empty($form_state['programmed'])) {
 489        $elements[$delta] = $element;
 490        $elements[$delta]['#default_value'] = $defaults;
 491        $elements[$delta]['#weight'] = $delta;
 492        $elements[$delta]['#required'] = ($element['#required'] && $delta == 0);
 493      }
 494      // The group of elements all-together need some extra functionality
 495      // after building up the full list (like draggable table rows).
 496      $elements['#file_upload_delta'] = $delta;
 497      $elements['#theme'] = 'file_widget_multiple';
 498      $elements['#theme_wrappers'] = array('fieldset');
 499      $elements['#process'] = array('file_field_widget_process_multiple');
 500      $elements['#title'] = $element['#title'];
 501      $elements['#description'] = $element['#description'];
 502      $elements['#field_name'] = $element['#field_name'];
 503      $elements['#language'] = $element['#language'];
 504      $elements['#display_field'] = $field['settings']['display_field'];
 505  
 506      // Add some properties that will eventually be added to the file upload
 507      // field. These are added here so that they may be referenced easily through
 508      // a hook_form_alter().
 509      $elements['#file_upload_title'] = t('Add a new file');
 510      $elements['#file_upload_description'] = theme('file_upload_help', array('description' => '', 'upload_validators' => $elements[0]['#upload_validators']));
 511    }
 512  
 513    return $elements;
 514  }
 515  
 516  /**
 517   * Retrieves the upload validators for a file field.
 518   *
 519   * @param $field
 520   *   A field array.
 521   *
 522   * @return
 523   *   An array suitable for passing to file_save_upload() or the file field
 524   *   element's '#upload_validators' property.
 525   */
 526  function file_field_widget_upload_validators($field, $instance) {
 527    // Cap the upload size according to the PHP limit.
 528    $max_filesize = parse_size(file_upload_max_size());
 529    if (!empty($instance['settings']['max_filesize']) && parse_size($instance['settings']['max_filesize']) < $max_filesize) {
 530      $max_filesize = parse_size($instance['settings']['max_filesize']);
 531    }
 532  
 533    $validators = array();
 534  
 535    // There is always a file size limit due to the PHP server limit.
 536    $validators['file_validate_size'] = array($max_filesize);
 537  
 538    // Add the extension check if necessary.
 539    if (!empty($instance['settings']['file_extensions'])) {
 540      $validators['file_validate_extensions'] = array($instance['settings']['file_extensions']);
 541    }
 542  
 543    return $validators;
 544  }
 545  
 546  /**
 547   * Determines the URI for a file field instance.
 548   *
 549   * @param $field
 550   *   A field array.
 551   * @param $instance
 552   *   A field instance array.
 553   * @param $data
 554   *   An array of token objects to pass to token_replace().
 555   *
 556   * @return
 557   *   A file directory URI with tokens replaced.
 558   *
 559   * @see token_replace()
 560   */
 561  function file_field_widget_uri($field, $instance, $data = array()) {
 562    $destination = trim($instance['settings']['file_directory'], '/');
 563  
 564    // Replace tokens.
 565    $destination = token_replace($destination, $data);
 566  
 567    return $field['settings']['uri_scheme'] . '://' . $destination;
 568  }
 569  
 570  /**
 571   * The #value_callback for the file_generic field element.
 572   */
 573  function file_field_widget_value($element, $input = FALSE, $form_state) {
 574    if ($input) {
 575      // Checkboxes lose their value when empty.
 576      // If the display field is present make sure its unchecked value is saved.
 577      $field = field_widget_field($element, $form_state);
 578      if (empty($input['display'])) {
 579        $input['display'] = $field['settings']['display_field'] ? 0 : 1;
 580      }
 581    }
 582  
 583    // We depend on the managed file element to handle uploads.
 584    $return = file_managed_file_value($element, $input, $form_state);
 585  
 586    // Ensure that all the required properties are returned even if empty.
 587    $return += array(
 588      'fid' => 0,
 589      'display' => 1,
 590      'description' => '',
 591    );
 592  
 593    return $return;
 594  }
 595  
 596  /**
 597   * An element #process callback for the file_generic field type.
 598   *
 599   * Expands the file_generic type to include the description and display fields.
 600   */
 601  function file_field_widget_process($element, &$form_state, $form) {
 602    $item = $element['#value'];
 603    $item['fid'] = $element['fid']['#value'];
 604  
 605    $field = field_widget_field($element, $form_state);
 606    $instance = field_widget_instance($element, $form_state);
 607    $settings = $instance['widget']['settings'];
 608  
 609    $element['#theme'] = 'file_widget';
 610  
 611    // Add the display field if enabled.
 612    if (!empty($field['settings']['display_field']) && $item['fid']) {
 613      $element['display'] = array(
 614        '#type' => empty($item['fid']) ? 'hidden' : 'checkbox',
 615        '#title' => t('Include file in display'),
 616        '#value' => isset($item['display']) ? $item['display'] : $field['settings']['display_default'],
 617        '#attributes' => array('class' => array('file-display')),
 618      );
 619    }
 620    else {
 621      $element['display'] = array(
 622        '#type' => 'hidden',
 623        '#value' => '1',
 624      );
 625    }
 626  
 627    // Add the description field if enabled.
 628    if (!empty($instance['settings']['description_field']) && $item['fid']) {
 629      $element['description'] = array(
 630        '#type' => variable_get('file_description_type', 'textfield'),
 631        '#title' => t('Description'),
 632        '#value' => isset($item['description']) ? $item['description'] : '',
 633        '#maxlength' => variable_get('file_description_length', 128),
 634        '#description' => t('The description may be used as the label of the link to the file.'),
 635      );
 636    }
 637  
 638    // Adjust the Ajax settings so that on upload and remove of any individual
 639    // file, the entire group of file fields is updated together.
 640    if ($field['cardinality'] != 1) {
 641      $parents = array_slice($element['#array_parents'], 0, -1);
 642      $new_path = 'file/ajax/' . implode('/', $parents) . '/' . $form['form_build_id']['#value'];
 643      $field_element = drupal_array_get_nested_value($form, $parents);
 644      $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
 645      foreach (element_children($element) as $key) {
 646        if (isset($element[$key]['#ajax'])) {
 647          $element[$key]['#ajax']['path'] = $new_path;
 648          $element[$key]['#ajax']['wrapper'] = $new_wrapper;
 649        }
 650      }
 651      unset($element['#prefix'], $element['#suffix']);
 652    }
 653  
 654    // Add another submit handler to the upload and remove buttons, to implement
 655    // functionality needed by the field widget. This submit handler, along with
 656    // the rebuild logic in file_field_widget_form() requires the entire field,
 657    // not just the individual item, to be valid.
 658    foreach (array('upload_button', 'remove_button') as $key) {
 659      $element[$key]['#submit'][] = 'file_field_widget_submit';
 660      $element[$key]['#limit_validation_errors'] = array(array_slice($element['#parents'], 0, -1));
 661    }
 662  
 663    return $element;
 664  }
 665  
 666  /**
 667   * An element #process callback for a group of file_generic fields.
 668   *
 669   * Adds the weight field to each row so it can be ordered and adds a new Ajax
 670   * wrapper around the entire group so it can be replaced all at once.
 671   */
 672  function file_field_widget_process_multiple($element, &$form_state, $form) {
 673    $element_children = element_children($element, TRUE);
 674    $count = count($element_children);
 675  
 676    foreach ($element_children as $delta => $key) {
 677      if ($key != $element['#file_upload_delta']) {
 678        $description = _file_field_get_description_from_element($element[$key]);
 679        $element[$key]['_weight'] = array(
 680          '#type' => 'weight',
 681          '#title' => $description ? t('Weight for @title', array('@title' => $description)) : t('Weight for new file'),
 682          '#title_display' => 'invisible',
 683          '#delta' => $count,
 684          '#default_value' => $delta,
 685        );
 686      }
 687      else {
 688        // The title needs to be assigned to the upload field so that validation
 689        // errors include the correct widget label.
 690        $element[$key]['#title'] = $element['#title'];
 691        $element[$key]['_weight'] = array(
 692          '#type' => 'hidden',
 693          '#default_value' => $delta,
 694        );
 695      }
 696    }
 697  
 698    // Add a new wrapper around all the elements for Ajax replacement.
 699    $element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
 700    $element['#suffix'] = '</div>';
 701  
 702    return $element;
 703  }
 704  
 705  /**
 706   * Retrieves the file description from a field field element.
 707   *
 708   * This helper function is used by file_field_widget_process_multiple().
 709   *
 710   * @param $element
 711   *   The element being processed.
 712   *
 713   * @return
 714   *   A description of the file suitable for use in the administrative interface.
 715   */
 716  function _file_field_get_description_from_element($element) {
 717    // Use the actual file description, if it's available.
 718    if (!empty($element['#default_value']['description'])) {
 719      return $element['#default_value']['description'];
 720    }
 721    // Otherwise, fall back to the filename.
 722    if (!empty($element['#default_value']['filename'])) {
 723      return $element['#default_value']['filename'];
 724    }
 725    // This is probably a newly uploaded file; no description is available.
 726    return FALSE;
 727  }
 728  
 729  /**
 730   * Form submission handler for upload/remove button of file_field_widget_form().
 731   *
 732   * This runs in addition to and after file_managed_file_submit().
 733   *
 734   * @see file_managed_file_submit()
 735   * @see file_field_widget_form()
 736   * @see file_field_widget_process()
 737   */
 738  function file_field_widget_submit($form, &$form_state) {
 739    // During the form rebuild, file_field_widget_form() will create field item
 740    // widget elements using re-indexed deltas, so clear out $form_state['input']
 741    // to avoid a mismatch between old and new deltas. The rebuilt elements will
 742    // have #default_value set appropriately for the current state of the field,
 743    // so nothing is lost in doing this.
 744    $parents = array_slice($form_state['triggering_element']['#parents'], 0, -2);
 745    drupal_array_set_nested_value($form_state['input'], $parents, NULL);
 746  
 747    $button = $form_state['triggering_element'];
 748  
 749    // Go one level up in the form, to the widgets container.
 750    $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
 751    $field_name = $element['#field_name'];
 752    $langcode = $element['#language'];
 753    $parents = $element['#field_parents'];
 754  
 755    $submitted_values = drupal_array_get_nested_value($form_state['values'], array_slice($button['#array_parents'], 0, -2));
 756    foreach ($submitted_values as $delta => $submitted_value) {
 757      if (!$submitted_value['fid']) {
 758        unset($submitted_values[$delta]);
 759      }
 760    }
 761  
 762    // Re-index deltas after removing empty items.
 763    $submitted_values = array_values($submitted_values);
 764  
 765    // Update form_state values.
 766    drupal_array_set_nested_value($form_state['values'], array_slice($button['#array_parents'], 0, -2), $submitted_values);
 767  
 768    // Update items.
 769    $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
 770    $field_state['items'] = $submitted_values;
 771    field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
 772  }
 773  
 774  /**
 775   * Returns HTML for an individual file upload widget.
 776   *
 777   * @param $variables
 778   *   An associative array containing:
 779   *   - element: A render element representing the widget.
 780   *
 781   * @ingroup themeable
 782   */
 783  function theme_file_widget($variables) {
 784    $element = $variables['element'];
 785    $output = '';
 786  
 787    // The "form-managed-file" class is required for proper Ajax functionality.
 788    $output .= '<div class="file-widget form-managed-file clearfix">';
 789    if ($element['fid']['#value'] != 0) {
 790      // Add the file size after the file name.
 791      $element['filename']['#markup'] .= ' <span class="file-size">(' . format_size($element['#file']->filesize) . ')</span> ';
 792    }
 793    $output .= drupal_render_children($element);
 794    $output .= '</div>';
 795  
 796    return $output;
 797  }
 798  
 799  /**
 800   * Returns HTML for a group of file upload widgets.
 801   *
 802   * @param $variables
 803   *   An associative array containing:
 804   *   - element: A render element representing the widgets.
 805   *
 806   * @ingroup themeable
 807   */
 808  function theme_file_widget_multiple($variables) {
 809    $element = $variables['element'];
 810  
 811    // Special ID and classes for draggable tables.
 812    $weight_class = $element['#id'] . '-weight';
 813    $table_id = $element['#id'] . '-table';
 814  
 815    // Build up a table of applicable fields.
 816    $headers = array();
 817    $headers[] = t('File information');
 818    if ($element['#display_field']) {
 819      $headers[] = array(
 820        'data' => t('Display'),
 821        'class' => array('checkbox'),
 822      );
 823    }
 824    $headers[] = t('Weight');
 825    $headers[] = t('Operations');
 826  
 827    // Get our list of widgets in order (needed when the form comes back after
 828    // preview or failed validation).
 829    $widgets = array();
 830    foreach (element_children($element) as $key) {
 831      $widgets[] = &$element[$key];
 832    }
 833    usort($widgets, '_field_sort_items_value_helper');
 834  
 835    $rows = array();
 836    foreach ($widgets as $key => &$widget) {
 837      // Save the uploading row for last.
 838      if ($widget['#file'] == FALSE) {
 839        $widget['#title'] = $element['#file_upload_title'];
 840        $widget['#description'] = $element['#file_upload_description'];
 841        continue;
 842      }
 843  
 844      // Delay rendering of the buttons, so that they can be rendered later in the
 845      // "operations" column.
 846      $operations_elements = array();
 847      foreach (element_children($widget) as $sub_key) {
 848        if (isset($widget[$sub_key]['#type']) && $widget[$sub_key]['#type'] == 'submit') {
 849          hide($widget[$sub_key]);
 850          $operations_elements[] = &$widget[$sub_key];
 851        }
 852      }
 853  
 854      // Delay rendering of the "Display" option and the weight selector, so that
 855      // each can be rendered later in its own column.
 856      if ($element['#display_field']) {
 857        hide($widget['display']);
 858      }
 859      hide($widget['_weight']);
 860  
 861      // Render everything else together in a column, without the normal wrappers.
 862      $widget['#theme_wrappers'] = array();
 863      $information = drupal_render($widget);
 864  
 865      // Render the previously hidden elements, using render() instead of
 866      // drupal_render(), to undo the earlier hide().
 867      $operations = '';
 868      foreach ($operations_elements as $operation_element) {
 869        $operations .= render($operation_element);
 870      }
 871      $display = '';
 872      if ($element['#display_field']) {
 873        unset($widget['display']['#title']);
 874        $display = array(
 875          'data' => render($widget['display']),
 876          'class' => array('checkbox'),
 877        );
 878      }
 879      $widget['_weight']['#attributes']['class'] = array($weight_class);
 880      $weight = render($widget['_weight']);
 881  
 882      // Arrange the row with all of the rendered columns.
 883      $row = array();
 884      $row[] = $information;
 885      if ($element['#display_field']) {
 886        $row[] = $display;
 887      }
 888      $row[] = $weight;
 889      $row[] = $operations;
 890      $rows[] = array(
 891        'data' => $row,
 892        'class' => isset($widget['#attributes']['class']) ? array_merge($widget['#attributes']['class'], array('draggable')) : array('draggable'),
 893      );
 894    }
 895  
 896    drupal_add_tabledrag($table_id, 'order', 'sibling', $weight_class);
 897  
 898    $output = '';
 899    $output = empty($rows) ? '' : theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('id' => $table_id)));
 900    $output .= drupal_render_children($element);
 901    return $output;
 902  }
 903  
 904  
 905  /**
 906   * Returns HTML for help text based on file upload validators.
 907   *
 908   * @param $variables
 909   *   An associative array containing:
 910   *   - description: The normal description for this field, specified by the
 911   *     user.
 912   *   - upload_validators: An array of upload validators as used in
 913   *     $element['#upload_validators'].
 914   *
 915   * @ingroup themeable
 916   */
 917  function theme_file_upload_help($variables) {
 918    $description = $variables['description'];
 919    $upload_validators = $variables['upload_validators'];
 920  
 921    $descriptions = array();
 922  
 923    if (strlen($description)) {
 924      $descriptions[] = $description;
 925    }
 926    if (isset($upload_validators['file_validate_size'])) {
 927      $descriptions[] = t('Files must be less than !size.', array('!size' => '<strong>' . format_size($upload_validators['file_validate_size'][0]) . '</strong>'));
 928    }
 929    if (isset($upload_validators['file_validate_extensions'])) {
 930      $descriptions[] = t('Allowed file types: !extensions.', array('!extensions' => '<strong>' . check_plain($upload_validators['file_validate_extensions'][0]) . '</strong>'));
 931    }
 932    if (isset($upload_validators['file_validate_image_resolution'])) {
 933      $max = $upload_validators['file_validate_image_resolution'][0];
 934      $min = $upload_validators['file_validate_image_resolution'][1];
 935      if ($min && $max && $min == $max) {
 936        $descriptions[] = t('Images must be exactly !size pixels.', array('!size' => '<strong>' . $max . '</strong>'));
 937      }
 938      elseif ($min && $max) {
 939        $descriptions[] = t('Images must be between !min and !max pixels.', array('!min' => '<strong>' . $min . '</strong>', '!max' => '<strong>' . $max . '</strong>'));
 940      }
 941      elseif ($min) {
 942        $descriptions[] = t('Images must be larger than !min pixels.', array('!min' => '<strong>' . $min . '</strong>'));
 943      }
 944      elseif ($max) {
 945        $descriptions[] = t('Images must be smaller than !max pixels.', array('!max' => '<strong>' . $max . '</strong>'));
 946      }
 947    }
 948  
 949    return implode('<br />', $descriptions);
 950  }
 951  
 952  /**
 953   * Implements hook_field_formatter_view().
 954   */
 955  function file_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
 956    $element = array();
 957  
 958    switch ($display['type']) {
 959      case 'file_default':
 960        foreach ($items as $delta => $item) {
 961          $element[$delta] = array(
 962            '#theme' => 'file_link',
 963            '#file' => (object) $item,
 964          );
 965        }
 966        break;
 967  
 968      case 'file_url_plain':
 969        foreach ($items as $delta => $item) {
 970          $element[$delta] = array('#markup' => empty($item['uri']) ? '' : file_create_url($item['uri']));
 971        }
 972        break;
 973  
 974      case 'file_table':
 975        if (!empty($items)) {
 976          // Display all values in a single element..
 977          $element[0] = array(
 978            '#theme' => 'file_formatter_table',
 979            '#items' => $items,
 980          );
 981        }
 982        break;
 983    }
 984  
 985    return $element;
 986  }
 987  
 988  /**
 989   * Returns HTML for a file attachments table.
 990   *
 991   * @param $variables
 992   *   An associative array containing:
 993   *   - items: An array of file attachments.
 994   *
 995   * @ingroup themeable
 996   */
 997  function theme_file_formatter_table($variables) {
 998    $header = array(t('Attachment'), t('Size'));
 999    $rows = array();
1000    foreach ($variables['items'] as $delta => $item) {
1001      $rows[] = array(
1002        theme('file_link', array('file' => (object) $item)),
1003        format_size($item['filesize']),
1004      );
1005    }
1006  
1007    return empty($rows) ? '' : theme('table', array('header' => $header, 'rows' => $rows));
1008  }

title

Description

title

Description

title

Description

title

title

Body