[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/modules/filefield/ -> filefield.module (source)

   1  <?php
   2  // $Id: filefield.module,v 1.231 2010/12/12 23:37:11 quicksketch Exp $
   3  
   4  /**
   5   * @file
   6   * FileField: Defines a CCK file field type.
   7   *
   8   * Uses content.module to store the fid and field specific metadata,
   9   * and Drupal's {files} table to store the actual file data.
  10   */
  11  
  12  // FileField API hooks should always be available.
  13  require_once dirname(__FILE__) . '/field_file.inc';
  14  require_once dirname(__FILE__) . '/filefield_widget.inc';
  15  
  16  /**
  17   * Implementation of hook_init().
  18   */
  19  function filefield_init() {
  20    // File hooks and callbacks may be used by any module.
  21    drupal_add_css(drupal_get_path('module', 'filefield') .'/filefield.css');
  22  
  23    // Conditional module support.
  24    if (module_exists('token')) {
  25      module_load_include('inc', 'filefield', 'filefield.token');
  26    }
  27  }
  28  
  29  /**
  30   * Implementation of hook_menu().
  31   */
  32  function filefield_menu() {
  33    $items = array();
  34  
  35    $items['filefield/ahah/%/%/%'] = array(
  36      'page callback' => 'filefield_js',
  37      'page arguments' => array(2, 3, 4),
  38      'access callback' => 'filefield_edit_access',
  39      'access arguments' => array(2, 3),
  40      'type' => MENU_CALLBACK,
  41    );
  42    $items['filefield/progress'] = array(
  43      'page callback' => 'filefield_progress',
  44      'access arguments' => array('access content'),
  45      'type' => MENU_CALLBACK,
  46    );
  47  
  48    return $items;
  49  }
  50  
  51  /**
  52   * Implementation of hook_elements().
  53   */
  54  function filefield_elements() {
  55    $elements = array();
  56    $elements['filefield_widget'] = array(
  57      '#input' => TRUE,
  58      '#columns' => array('fid', 'list', 'data'),
  59      '#process' => array('filefield_widget_process'),
  60      '#value_callback' => 'filefield_widget_value',
  61      '#element_validate' => array('filefield_widget_validate'),
  62    );
  63    return $elements;
  64  }
  65  
  66  /**
  67   * Implementation of hook_theme().
  68   * @todo: autogenerate theme registry entrys for widgets.
  69   */
  70  function filefield_theme() {
  71    return array(
  72      'filefield_file' => array(
  73        'arguments' => array('file' => NULL),
  74        'file' => 'filefield_formatter.inc',
  75      ),
  76      'filefield_icon' => array(
  77        'arguments' => array('file' => NULL),
  78        'file' => 'filefield.theme.inc',
  79      ),
  80      'filefield_widget' => array(
  81        'arguments' => array('element' => NULL),
  82        'file' => 'filefield_widget.inc',
  83      ),
  84      'filefield_widget_item' => array(
  85        'arguments' => array('element' => NULL),
  86        'file' => 'filefield_widget.inc',
  87      ),
  88      'filefield_widget_preview' => array(
  89        'arguments' => array('element' => NULL),
  90        'file' => 'filefield_widget.inc',
  91      ),
  92      'filefield_widget_file' => array(
  93        'arguments' => array('element' => NULL),
  94        'file' => 'filefield_widget.inc',
  95      ),
  96  
  97  
  98      'filefield_formatter_default' => array(
  99        'arguments' => array('element' => NULL),
 100        'file' => 'filefield_formatter.inc',
 101      ),
 102      'filefield_formatter_url_plain' => array(
 103        'arguments' => array('element' => NULL),
 104        'file' => 'filefield_formatter.inc',
 105      ),
 106      'filefield_formatter_path_plain' => array(
 107        'arguments' => array('element' => NULL),
 108        'file' => 'filefield_formatter.inc',
 109      ),
 110      'filefield_item' => array(
 111        'arguments' => array('file' => NULL, 'field' => NULL),
 112        'file' => 'filefield_formatter.inc',
 113      ),
 114      'filefield_file' => array(
 115        'arguments' => array('file' => NULL),
 116        'file' => 'filefield_formatter.inc',
 117      ),
 118  
 119   );
 120  }
 121  
 122  /**
 123   * Implementation of hook_file_download().
 124   */
 125  function filefield_file_download($filepath) {
 126    $filepath = file_create_path($filepath);
 127    $result = db_query("SELECT * FROM {files} WHERE filepath = '%s'", $filepath);
 128  
 129    // Ensure case-sensitivity of uploaded file names.
 130    while ($file = db_fetch_object($result)) {
 131      if (strcmp($file->filepath, $filepath) == 0) {
 132        break;
 133      }
 134    }
 135  
 136    // If the file is not found in the database, we're not responsible for it.
 137    if (empty($file)) {
 138      return;
 139    }
 140  
 141    // See if this is a file on a newly created node, on which the user who
 142    // uploaded it will immediately have access.
 143    $new_node_file = $file->status == 0 && isset($_SESSION['filefield_access']) && in_array($file->fid, $_SESSION['filefield_access']);
 144    if ($new_node_file) {
 145      $denied = FALSE;
 146    }
 147    // Loop through all fields and find if this file is used by FileField.
 148    else {
 149      // Find out if any file field contains this file, and if so, which field
 150      // and node it belongs to. Required for later access checking.
 151      $cck_files = array();
 152      foreach (content_fields() as $field) {
 153        if ($field['type'] == 'filefield' || $field['type'] == 'image') {
 154          $db_info = content_database_info($field);
 155          $table = $db_info['table'];
 156          $fid_column = $db_info['columns']['fid']['column'];
 157  
 158          $columns = array('vid', 'nid');
 159          foreach ($db_info['columns'] as $property_name => $column_info) {
 160            $columns[] = $column_info['column'] .' AS '. $property_name;
 161          }
 162          $result = db_query("SELECT ". implode(', ', $columns) ."
 163                              FROM {". $table ."}
 164                              WHERE ". $fid_column ." = %d", $file->fid);
 165  
 166          while ($content = db_fetch_array($result)) {
 167            $content['field'] = $field;
 168            $cck_files[$field['field_name']][$content['vid']] = $content;
 169          }
 170        }
 171      }
 172  
 173      // If no file field item is involved with this file, we don't care about it.
 174      if (empty($cck_files)) {
 175        return;
 176      }
 177  
 178      // So the overall field view permissions are not denied, but if access is
 179      // denied for ALL nodes containing the file, deny the download as well.
 180      // Node access checks also include checking for 'access content'.
 181      $nodes = array();
 182      $denied = TRUE;
 183      foreach ($cck_files as $field_name => $field_files) {
 184        foreach ($field_files as $revision_id => $content) {
 185          // Checking separately for each revision is probably not the best idea -
 186          // what if 'view revisions' is disabled? So, let's just check for the
 187          // current revision of that node.
 188          if (isset($nodes[$content['nid']])) {
 189            continue; // Don't check the same node twice.
 190          }
 191          if (($node = node_load($content['nid'])) && (node_access('view', $node) && filefield_view_access($field_name, $node))) {
 192            $denied = FALSE;
 193            break 2;
 194          }
 195          $nodes[$content['nid']] = $node;
 196        }
 197      }
 198    }
 199  
 200    if ($denied) {
 201      return -1;
 202    }
 203  
 204    // Access is granted.
 205    $name = mime_header_encode($file->filename);
 206    $type = mime_header_encode($file->filemime);
 207    // By default, serve images, text, and flash content for display rather than
 208    // download. Or if variable 'filefield_inline_types' is set, use its patterns.
 209    $inline_types = variable_get('filefield_inline_types', array('^text/', '^image/', 'flash$'));
 210    $disposition = 'attachment';
 211    foreach ($inline_types as $inline_type) {
 212      // Exclamation marks are used as delimiters to avoid escaping slashes.
 213      if (preg_match('!' . $inline_type . '!', $file->filemime)) {
 214        $disposition = 'inline';
 215      }
 216    }
 217    return array(
 218      'Content-Type: ' . $type . '; name="' . $name . '"',
 219      'Content-Length: ' . $file->filesize,
 220      'Content-Disposition: ' . $disposition . '; filename="' . $name . '"',
 221      'Cache-Control: private',
 222    );
 223  }
 224  
 225  /**
 226   * Implementation of hook_views_api().
 227   */
 228  function filefield_views_api() {
 229    return array(
 230      'api' => 2.0,
 231      'path' => drupal_get_path('module', 'filefield') . '/views',
 232    );
 233  }
 234  
 235  /**
 236   * Implementation of hook_form_alter().
 237   *
 238   * Set the enctype on forms that need to accept file uploads.
 239   */
 240  function filefield_form_alter(&$form, $form_state, $form_id) {
 241    // Field configuration (for default images).
 242    if ($form_id == 'content_field_edit_form' && isset($form['#field']) && $form['#field']['type'] == 'filefield') {
 243      $form['#attributes']['enctype'] = 'multipart/form-data';
 244    }
 245  
 246    // Node forms.
 247    if (preg_match('/_node_form$/', $form_id)) {
 248      $form['#attributes']['enctype'] = 'multipart/form-data';
 249    }
 250  }
 251  
 252  /**
 253   * Implementation of CCK's hook_field_info().
 254   */
 255  function filefield_field_info() {
 256    return array(
 257      'filefield' => array(
 258        'label' => t('File'),
 259        'description' => t('Store an arbitrary file.'),
 260      ),
 261    );
 262  }
 263  
 264  /**
 265   * Implementation of hook_field_settings().
 266   */
 267  function filefield_field_settings($op, $field) {
 268    $return = array();
 269  
 270    module_load_include('inc', 'filefield', 'filefield_field');
 271    $op = str_replace(' ', '_', $op);
 272    $function = 'filefield_field_settings_'. $op;
 273    if (function_exists($function)) {
 274      $result = $function($field);
 275      if (isset($result) && is_array($result)) {
 276        $return = $result;
 277      }
 278    }
 279  
 280    return $return;
 281  
 282  }
 283  
 284  /**
 285   * Implementation of CCK's hook_field().
 286   */
 287  function filefield_field($op, $node, $field, &$items, $teaser, $page) {
 288    module_load_include('inc', 'filefield', 'filefield_field');
 289    $op = str_replace(' ', '_', $op);
 290    // add filefield specific handlers...
 291    $function = 'filefield_field_'. $op;
 292    if (function_exists($function)) {
 293      return $function($node, $field, $items, $teaser, $page);
 294    }
 295  }
 296  
 297  /**
 298   * Implementation of CCK's hook_widget_settings().
 299   */
 300  function filefield_widget_settings($op, $widget) {
 301    switch ($op) {
 302      case 'form':
 303        return filefield_widget_settings_form($widget);
 304      case 'save':
 305        return filefield_widget_settings_save($widget);
 306    }
 307  }
 308  
 309  /**
 310   * Implementation of hook_widget().
 311   */
 312  function filefield_widget(&$form, &$form_state, $field, $items, $delta = 0) {
 313    if (module_exists('devel_themer') && (user_access('access devel theme information') || user_access('access devel information'))) {
 314      drupal_set_message(t('Files may not be uploaded while the Theme Developer tool is enabled. It is highly recommended to <a href="!url">disable this module</a> unless it is actively being used.', array('!url' => url('admin/build/modules'))), 'error');
 315    }
 316  
 317    // CCK doesn't give a validate callback at the field level...
 318    // and FAPI's #require is naive to complex structures...
 319    // we validate at the field level ourselves.
 320    if (empty($form['#validate']) || !in_array('filefield_node_form_validate', $form['#validate'])) {
 321      $form['#validate'][] = 'filefield_node_form_validate';
 322    }
 323    $form['#attributes']['enctype'] = 'multipart/form-data';
 324  
 325    module_load_include('inc', 'filefield', 'field_widget');
 326    module_load_include('inc', $field['widget']['module'], $field['widget']['module'] .'_widget');
 327  
 328    $item = array('fid' => 0, 'list' => $field['list_default'], 'data' => array('description' => ''));
 329    if (isset($items[$delta])) {
 330      $item = array_merge($item, $items[$delta]);
 331    }
 332    $element = array(
 333      '#title' => $field['widget']['label'],
 334      '#type' => $field['widget']['type'],
 335      '#default_value' => $item,
 336      '#upload_validators' => filefield_widget_upload_validators($field),
 337    );
 338  
 339    return $element;
 340  }
 341  
 342  /**
 343   * Get the upload validators for a file field.
 344   *
 345   * @param $field
 346   *   A CCK field array.
 347   * @return
 348   *   An array suitable for passing to file_save_upload() or the file field
 349   *   element's '#upload_validators' property.
 350   */
 351  function filefield_widget_upload_validators($field) {
 352    $max_filesize = parse_size(file_upload_max_size());
 353    if (!empty($field['widget']['max_filesize_per_file']) && parse_size($field['widget']['max_filesize_per_file']) < $max_filesize) {
 354      $max_filesize = parse_size($field['widget']['max_filesize_per_file']);
 355    }
 356  
 357    // Match the default value if no file extensions have been saved at all.
 358    if (!isset($field['widget']['file_extensions'])) {
 359      $field['widget']['file_extensions'] = 'txt';
 360    }
 361  
 362    $validators = array(
 363      // associate the field to the file on validation.
 364      'filefield_validate_associate_field' => array($field),
 365      'filefield_validate_size' => array($max_filesize),
 366      // Override core since it excludes uid 1 on the extension check.
 367      // Filefield only excuses uid 1 of quota requirements.
 368      'filefield_validate_extensions' => array($field['widget']['file_extensions']),
 369    );
 370    return $validators;
 371  }
 372  
 373  /**
 374   * Implementation of CCK's hook_content_is_empty().
 375   *
 376   * The result of this determines whether content.module will save the value of
 377   * the field. Note that content module has some interesting behaviors for empty
 378   * values. It will always save at least one record for every node revision,
 379   * even if the values are all NULL. If it is a multi-value field with an
 380   * explicit limit, CCK will save that number of empty entries.
 381   */
 382  function filefield_content_is_empty($item, $field) {
 383    return empty($item['fid']) || (int)$item['fid'] == 0;
 384  }
 385  
 386  /**
 387   * Implementation of CCK's hook_content_diff_values().
 388   */
 389  function filefield_content_diff_values($node, $field, $items) {
 390    $return = array();
 391    foreach ($items as $item) {
 392      if (is_array($item) && !empty($item['filepath'])) {
 393        $return[] = $item['filepath'];
 394      }
 395    }
 396    return $return;
 397  }
 398  
 399  /**
 400   * Implementation of CCK's hook_default_value().
 401   *
 402   * Note this is a widget-level hook, so it does not affect ImageField or other
 403   * modules that extend FileField.
 404   *
 405   * @see content_default_value()
 406   */
 407  function filefield_default_value(&$form, &$form_state, $field, $delta) {
 408    // Reduce the default number of upload fields to one. CCK 2 (but not 3) will
 409    // automatically add one more field than necessary. We use the
 410    // content_multiple_value_after_build function to determine the version.
 411    if (!function_exists('content_multiple_value_after_build') && !isset($form_state['item_count'][$field['field_name']])) {
 412      $form_state['item_count'][$field['field_name']] = 0;
 413    }
 414  
 415    // The default value is actually handled in hook_widget().
 416    // hook_default_value() is only helpful for new nodes, and we need to affect
 417    // all widgets, such as when a new field is added via "Add another item".
 418    return array();
 419  }
 420  
 421  /**
 422   * Implementation of CCK's hook_widget_info().
 423   */
 424  function filefield_widget_info() {
 425    return array(
 426      'filefield_widget' => array(
 427        'label' => t('File Upload'),
 428        'field types' => array('filefield'),
 429        'multiple values' => CONTENT_HANDLE_CORE,
 430        'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM),
 431        'description' => t('A plain file upload widget.'),
 432        'file_extensions' => 'txt',
 433      ),
 434    );
 435  }
 436  
 437  /**
 438   * Implementation of CCK's hook_field_formatter_info().
 439   */
 440  function filefield_field_formatter_info() {
 441    return array(
 442      'default' => array(
 443        'label' => t('Generic files'),
 444        'field types' => array('filefield'),
 445        'multiple values' => CONTENT_HANDLE_CORE,
 446        'description' => t('Displays all kinds of files with an icon and a linked file description.'),
 447      ),
 448      'path_plain' => array(
 449        'label' => t('Path to file'),
 450        'field types' => array('filefield'),
 451        'description' => t('Displays the file system path to the file.'),
 452      ),
 453      'url_plain' => array(
 454        'label' => t('URL to file'),
 455        'field types' => array('filefield'),
 456        'description' => t('Displays a full URL to the file.'),
 457      ),
 458    );
 459  }
 460  
 461  /**
 462   * Implementation of CCK's hook_content_generate(). Used by generate.module.
 463   */
 464  function filefield_content_generate($node, $field) {
 465    module_load_include('inc', 'filefield', 'filefield.devel');
 466  
 467    if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_MODULE) {
 468      return content_devel_multiple('_filefield_content_generate', $node, $field);
 469    }
 470    else {
 471      return _filefield_content_generate($node, $field);
 472    }
 473  }
 474  
 475  /**
 476   * Get a list of possible information stored in a file field "data" column.
 477   */
 478  function filefield_data_info() {
 479    static $columns;
 480  
 481    if (!isset($columns)) {
 482      $columns = array();
 483      foreach (module_implements('filefield_data_info') as $module) {
 484        $function = $module . '_filefield_data_info';
 485        $data = (array) $function();
 486        foreach ($data as $key => $value) {
 487          $data[$key] = $value;
 488          $data[$key]['module'] = $module;
 489        }
 490        $columns = array_merge($columns, $data);
 491      }
 492    }
 493  
 494    return $columns;
 495  }
 496  
 497  /**
 498   * Given an array of data options, dispatch the necessary callback function.
 499   */
 500  function filefield_data_value($key, $value) {
 501    $info = filefield_data_info();
 502    if (isset($info[$key]['callback'])) {
 503      $callback = $info[$key]['callback'];
 504      $value = $callback($value);
 505    }
 506    else {
 507      $value = check_plain((string) $value);
 508    }
 509    return $value;
 510  }
 511  
 512  /**
 513   * Implementation of hook_filefield_data_info().
 514   *
 515   * Define a list of values that this module stores in the "data" column of a
 516   * file field. The callback function receives the portion of the data column
 517   * defined by key and should return a value suitable for printing to the page.
 518   */
 519  function filefield_filefield_data_info() {
 520    return array(
 521      'description' => array(
 522        'title' => t('Description'),
 523        'callback' => 'check_plain',
 524      ),
 525    );
 526  }
 527  
 528  /**
 529   * Determine the most appropriate icon for the given file's mimetype.
 530   *
 531   * @param $file
 532   *   A file object.
 533   * @return
 534   *   The URL of the icon image file, or FALSE if no icon could be found.
 535   */
 536  function filefield_icon_url($file) {
 537    module_load_include('inc', 'filefield', 'filefield.theme');
 538    return _filefield_icon_url($file);
 539  }
 540  
 541  /**
 542   * Implementation of hook_filefield_icon_sets().
 543   *
 544   * Define a list of icon sets and directories that contain the icons.
 545   */
 546  function filefield_filefield_icon_sets() {
 547    return array(
 548      'default' => drupal_get_path('module', 'filefield') . '/icons',
 549    );
 550  }
 551  
 552  /**
 553   * Access callback for the JavaScript upload and deletion AHAH callbacks.
 554   *
 555   * The content_permissions module provides nice fine-grained permissions for
 556   * us to check, so we can make sure that the user may actually edit the file.
 557   */
 558  function filefield_edit_access($type_name, $field_name) {
 559    if (!content_access('edit', content_fields($field_name, $type_name))) {
 560      return FALSE;
 561    }
 562    // No content permissions to check, so let's fall back to a more general permission.
 563    return user_access('access content') || user_access('administer nodes');
 564  }
 565  
 566  /**
 567   * Access callback that checks if the current user may view the filefield.
 568   */
 569  function filefield_view_access($field_name, $node = NULL) {
 570    if (!content_access('view', content_fields($field_name), NULL, $node)) {
 571      return FALSE;
 572    }
 573    // No content permissions to check, so let's fall back to a more general permission.
 574    return user_access('access content') || user_access('administer nodes');
 575  }
 576  
 577  /**
 578   * Menu callback; Shared AHAH callback for uploads and deletions.
 579   *
 580   * This rebuilds the form element for a particular field item. As long as the
 581   * form processing is properly encapsulated in the widget element the form
 582   * should rebuild correctly using FAPI without the need for additional callbacks
 583   * or processing.
 584   */
 585  function filefield_js($type_name, $field_name, $delta) {
 586    $field = content_fields($field_name, $type_name);
 587  
 588    // Immediately disable devel shutdown functions so that it doesn't botch our
 589    // JSON output.
 590    $GLOBALS['devel_shutdown'] = FALSE;
 591  
 592    if (empty($field) || empty($_POST['form_build_id'])) {
 593      // Invalid request.
 594      drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
 595      print drupal_to_js(array('data' => theme('status_messages')));
 596      exit;
 597    }
 598  
 599    // Build the new form.
 600    $form_state = array('submitted' => FALSE);
 601    $form_build_id = $_POST['form_build_id'];
 602    $form = form_get_cache($form_build_id, $form_state);
 603  
 604    if (!$form) {
 605      // Invalid form_build_id.
 606      drupal_set_message(t('An unrecoverable error occurred. This form was missing from the server cache. Try reloading the page and submitting again.'), 'error');
 607      print drupal_to_js(array('data' => theme('status_messages')));
 608      exit;
 609    }
 610  
 611    // Build the form. This calls the file field's #value_callback function and
 612    // saves the uploaded file. Since this form is already marked as cached
 613    // (the #cache property is TRUE), the cache is updated automatically and we
 614    // don't need to call form_set_cache().
 615    $args = $form['#parameters'];
 616    $form_id = array_shift($args);
 617    $form['#post'] = $_POST;
 618    $form = form_builder($form_id, $form, $form_state);
 619  
 620    // Update the cached form with the new element at the right place in the form.
 621    if (module_exists('fieldgroup') && ($group_name = _fieldgroup_field_get_group($type_name, $field_name))) {
 622      if (isset($form['#multigroups']) && isset($form['#multigroups'][$group_name][$field_name])) {
 623        $form_element = $form[$group_name][$delta][$field_name];
 624      }
 625      else {
 626        $form_element = $form[$group_name][$field_name][$delta];
 627      }
 628    }
 629    else {
 630      $form_element = $form[$field_name][$delta];
 631    }
 632  
 633    if (isset($form_element['_weight'])) {
 634      unset($form_element['_weight']);
 635    }
 636  
 637    $output = drupal_render($form_element);
 638  
 639    // AHAH is not being nice to us and doesn't know the "other" button (that is,
 640    // either "Upload" or "Delete") yet. Which in turn causes it not to attach
 641    // AHAH behaviours after replacing the element. So we need to tell it first.
 642  
 643    // Loop through the JS settings and find the settings needed for our buttons.
 644    $javascript = drupal_add_js(NULL, NULL);
 645    $filefield_ahah_settings = array();
 646    if (isset($javascript['setting'])) {
 647      foreach ($javascript['setting'] as $settings) {
 648        if (isset($settings['ahah'])) {
 649          foreach ($settings['ahah'] as $id => $ahah_settings) {
 650            if (strpos($id, 'filefield-upload') || strpos($id, 'filefield-remove')) {
 651              $filefield_ahah_settings[$id] = $ahah_settings;
 652            }
 653          }
 654        }
 655      }
 656    }
 657  
 658    // Add the AHAH settings needed for our new buttons.
 659    if (!empty($filefield_ahah_settings)) {
 660      $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings.ahah, '. drupal_to_js($filefield_ahah_settings) .');</script>';
 661    }
 662  
 663    $output = theme('status_messages') . $output;
 664  
 665    // For some reason, file uploads don't like drupal_json() with its manual
 666    // setting of the text/javascript HTTP header. So use this one instead.
 667    print drupal_to_js(array('status' => TRUE, 'data' => $output));
 668    exit;
 669  }
 670  
 671  /**
 672   * Menu callback for upload progress.
 673   */
 674  function filefield_progress($key) {
 675    $progress = array(
 676      'message' => t('Starting upload...'),
 677      'percentage' => -1,
 678    );
 679  
 680    $implementation = filefield_progress_implementation();
 681    if ($implementation == 'uploadprogress') {
 682      $status = uploadprogress_get_info($key);
 683      if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
 684        $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['bytes_uploaded']), '@total' => format_size($status['bytes_total'])));
 685        $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']);
 686      }
 687    }
 688    elseif ($implementation == 'apc') {
 689      $status = apc_fetch('upload_' . $key);
 690      if (isset($status['current']) && !empty($status['total'])) {
 691        $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['current']), '@total' => format_size($status['total'])));
 692        $progress['percentage'] = round(100 * $status['current'] / $status['total']);
 693      }
 694    }
 695  
 696    drupal_json($progress);
 697  }
 698  
 699  /**
 700   * Determine which upload progress implementation to use, if any available.
 701   */
 702  function filefield_progress_implementation() {
 703    static $implementation;
 704    if (!isset($implementation)) {
 705      $implementation = FALSE;
 706  
 707      // We prefer the PECL extension uploadprogress because it supports multiple
 708      // simultaneous uploads. APC only supports one at a time.
 709      if (extension_loaded('uploadprogress')) {
 710        $implementation = 'uploadprogress';
 711      }
 712      elseif (extension_loaded('apc') && ini_get('apc.rfc1867')) {
 713        $implementation = 'apc';
 714      }
 715    }
 716    return $implementation;
 717  }
 718  
 719  /**
 720   * Implementation of hook_file_references().
 721   */
 722  function filefield_file_references($file) {
 723    $count = filefield_get_file_reference_count($file);
 724    return $count ? array('filefield' => $count) : NULL;
 725  }
 726  
 727  /**
 728   * Implementation of hook_file_delete().
 729   */
 730  function filefield_file_delete($file) {
 731    filefield_delete_file_references($file);
 732  }
 733  
 734  /**
 735   * An #upload_validators callback. Check the file matches an allowed extension.
 736   *
 737   * If the mimedetect module is available, this will also validate that the
 738   * content of the file matches the extension. User #1 is included in this check.
 739   *
 740   * @param $file
 741   *   A Drupal file object.
 742   * @param $extensions
 743   *   A string with a space separated list of allowed extensions.
 744   * @return
 745   *   An array of any errors cause by this file if it failed validation.
 746   */
 747  function filefield_validate_extensions($file, $extensions) {
 748    global $user;
 749    $errors = array();
 750  
 751    if (!empty($extensions)) {
 752      $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
 753      $matches = array();
 754      if (preg_match($regex, $file->filename, $matches)) {
 755        $extension = $matches[1];
 756        // If the extension validates, check that the mimetype matches.
 757        if (module_exists('mimedetect')) {
 758          $type = mimedetect_mime($file);
 759          if ($type != $file->filemime) {
 760            $errors[] = t('The file contents (@type) do not match its extension (@extension).', array('@type' => $type, '@extension' => $extension));
 761          }
 762        }
 763      }
 764      else {
 765        $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions));
 766      }
 767    }
 768  
 769    return $errors;
 770  }
 771  
 772  /**
 773   * Help text automatically appended to fields that have extension validation.
 774   */
 775  function filefield_validate_extensions_help($extensions) {
 776    if (!empty($extensions)) {
 777      return t('Allowed extensions: %ext', array('%ext' => $extensions));
 778    }
 779    else {
 780      return '';
 781    }
 782  }
 783  
 784  /**
 785   * An #upload_validators callback. Check the file size does not exceed a limit.
 786   *
 787   * @param $file
 788   *   A Drupal file object.
 789   * @param $file_limit
 790   *   An integer value limiting the maximum file size in bytes.
 791   * @param $file_limit
 792   *   An integer value limiting the maximum size in bytes a user can upload on
 793   *   the entire site.
 794   * @return
 795   *   An array of any errors cause by this file if it failed validation.
 796   */
 797  function filefield_validate_size($file, $file_limit = 0, $user_limit = 0) {
 798    global $user;
 799  
 800    $errors = array();
 801  
 802    if ($file_limit && $file->filesize > $file_limit) {
 803      $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit)));
 804    }
 805  
 806    // Bypass user limits for uid  = 1.
 807    if ($user->uid != 1) {
 808      $total_size = file_space_used($user->uid) + $file->filesize;
 809      if ($user_limit && $total_size > $user_limit) {
 810        $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit)));
 811      }
 812    }
 813    return $errors;
 814  }
 815  
 816  /**
 817   * Automatic help text appended to fields that have file size validation.
 818   */
 819  function filefield_validate_size_help($size) {
 820    return t('Maximum file size: %size', array('%size' => format_size(parse_size($size))));
 821  }
 822  
 823  /**
 824   * An #upload_validators callback. Check an image resolution.
 825   *
 826   * @param $file
 827   *   A Drupal file object.
 828   * @param $max_size
 829   *   A string in the format WIDTHxHEIGHT. If the image is larger than this size
 830   *   the image will be scaled to fit within these dimensions.
 831   * @param $min_size
 832   *   A string in the format WIDTHxHEIGHT. If the image is smaller than this size
 833   *   a validation error will be returned.
 834   * @return
 835   *   An array of any errors cause by this file if it failed validation.
 836   */
 837  function filefield_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimum_dimensions = 0) {
 838    $errors = array();
 839  
 840    @list($max_width, $max_height) = explode('x', $maximum_dimensions);
 841    @list($min_width, $min_height) = explode('x', $minimum_dimensions);
 842  
 843    // Check first that the file is an image.
 844    if ($info = image_get_info($file->filepath)) {
 845      if ($maximum_dimensions) {
 846        $resized = FALSE;
 847  
 848        // Check that it is smaller than the given dimensions.
 849        if ($info['width'] > $max_width || $info['height'] > $max_height) {
 850          $ratio = min($max_width/$info['width'], $max_height/$info['height']);
 851          // Check for exact dimension requirements (scaling allowed).
 852          if (strcmp($minimum_dimensions, $maximum_dimensions) == 0 && $info['width']/$max_width != $info['height']/$max_height) {
 853            $errors[] = t('The image must be exactly %dimensions pixels.', array('%dimensions' => $maximum_dimensions));
 854          }
 855          // Check that scaling won't drop the image below the minimum dimensions.
 856          elseif ((image_get_toolkit() || module_exists('imageapi')) && (($info['width'] * $ratio < $min_width) || ($info['height'] * $ratio < $min_height))) {
 857            $errors[] = t('The image will not fit between the dimensions of %min_dimensions and %max_dimensions pixels.', array('%min_dimensions' => $minimum_dimensions, '%max_dimensions' => $maximum_dimensions));
 858          }
 859          // Try resizing the image with ImageAPI if available.
 860          elseif (module_exists('imageapi') && imageapi_default_toolkit()) {
 861            $res = imageapi_image_open($file->filepath);
 862            imageapi_image_scale($res, $max_width, $max_height);
 863            imageapi_image_close($res, $file->filepath);
 864            $resized = TRUE;
 865          }
 866          // Try to resize the image to fit the dimensions.
 867          elseif (image_get_toolkit() && @image_scale($file->filepath, $file->filepath, $max_width, $max_height)) {
 868            $resized = TRUE;
 869          }
 870          else {
 871            $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions));
 872          }
 873        }
 874  
 875        // Clear the cached filesize and refresh the image information.
 876        if ($resized) {
 877          drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
 878          clearstatcache();
 879          $file->filesize = filesize($file->filepath);
 880        }
 881      }
 882  
 883      if ($minimum_dimensions && empty($errors)) {
 884        // Check that it is larger than the given dimensions.
 885        if ($info['width'] < $min_width || $info['height'] < $min_height) {
 886          $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions));
 887        }
 888      }
 889    }
 890  
 891    return $errors;
 892  }
 893  
 894  /**
 895   * Automatic help text appended to fields that have image resolution validation.
 896   */
 897  function filefield_validate_image_resolution_help($max_size = '0', $min_size = '0') {
 898    if (!empty($max_size)) {
 899      if (!empty($min_size)) {
 900        if ($max_size == $min_size) {
 901          return t('Images must be exactly @min_size pixels', array('@min_size' => $min_size));
 902        }
 903        else {
 904          return t('Images must be between @min_size pixels and @max_size', array('@max_size' => $max_size, '@min_size' => $min_size));
 905        }
 906      }
 907      else {
 908        if (image_get_toolkit()) {
 909          return t('Images larger than @max_size pixels will be scaled', array('@max_size' => $max_size));
 910        }
 911        else {
 912          return t('Images must be smaller than @max_size pixels', array('@max_size' => $max_size));
 913        }
 914      }
 915    }
 916    if (!empty($min_size)) {
 917      return t('Images must be larger than @max_size pixels', array('@max_size' => $min_size));
 918    }
 919  }
 920  
 921  
 922  /**
 923   * An #upload_validators callback. Check that a file is an image.
 924   *
 925   * This check should allow any image that PHP can identify, including png, jpg,
 926   * gif, tif, bmp, psd, swc, iff, jpc, jp2, jpx, jb2, xbm, and wbmp.
 927   *
 928   * This check should be combined with filefield_validate_extensions() to ensure
 929   * only web-based images are allowed, however it provides a better check than
 930   * extension checking alone if the mimedetect module is not available.
 931   *
 932   * @param $file
 933   *   A Drupal file object.
 934   * @return
 935   *   An array of any errors cause by this file if it failed validation.
 936   */
 937  function filefield_validate_is_image(&$file) {
 938    $errors = array();
 939    $info = image_get_info($file->filepath);
 940    if (!$info || empty($info['extension'])) {
 941      $errors[] = t('The file is not a known image format.');
 942    }
 943    return $errors;
 944  }
 945  
 946  /**
 947   * An #upload_validators callback. Add the field to the file object.
 948   *
 949   * This validation function adds the field to the file object for later
 950   * use in field aware modules implementing hook_file. It's not truly a
 951   * validation at all, rather a convient way to add properties to the uploaded
 952   * file.
 953   */
 954  function filefield_validate_associate_field(&$file, $field) {
 955    $file->field = $field;
 956    return array();
 957  }
 958  
 959  /*******************************************************************************
 960   * Public API functions for FileField.
 961   ******************************************************************************/
 962  
 963  /**
 964   * Return an array of file fields within a node type or by field name.
 965   *
 966   * @param $field
 967   *   Optional. May be either a field array or a field name.
 968   * @param $node_type
 969   *   Optional. The node type to filter the list of fields.
 970   */
 971  function filefield_get_field_list($node_type = NULL, $field = NULL) {
 972    // Build the list of fields to be used for retrieval.
 973    if (isset($field)) {
 974      if (is_string($field)) {
 975        $field = content_fields($field, $node_type);
 976      }
 977      $fields = array($field['field_name'] => $field);
 978    }
 979    elseif (isset($node_type)) {
 980      $type = content_types($node_type);
 981      $fields = $type['fields'];
 982    }
 983    else {
 984      $fields = content_fields();
 985    }
 986  
 987    // Filter down the list just to file fields.
 988    foreach ($fields as $key => $field) {
 989      if ($field['type'] != 'filefield') {
 990        unset($fields[$key]);
 991      }
 992    }
 993  
 994    return $fields;
 995  }
 996  
 997  /**
 998   * Count the number of times the file is referenced within a field.
 999   *
1000   * @param $file
1001   *   A file object.
1002   * @param $field
1003   *   Optional. The CCK field array or field name as a string.
1004   * @return
1005   *   An integer value.
1006   */
1007  function filefield_get_file_reference_count($file, $field = NULL) {
1008    $fields = filefield_get_field_list(NULL, $field);
1009    $file = (object) $file;
1010  
1011    $references = 0;
1012    foreach ($fields as $field) {
1013      $db_info = content_database_info($field);
1014      $references += db_result(db_query(
1015        'SELECT count('. $db_info['columns']['fid']['column'] .')
1016          FROM {'. $db_info['table'] .'}
1017          WHERE '. $db_info['columns']['fid']['column'] .' = %d', $file->fid
1018      ));
1019  
1020      // If a field_name is present in the file object, the file is being deleted
1021      // from this field.
1022      if (isset($file->field_name) && $field['field_name'] == $file->field_name) {
1023        // If deleting the entire node, count how many references to decrement.
1024        if (isset($file->delete_nid)) {
1025          $node_references = db_result(db_query(
1026            'SELECT count('. $db_info['columns']['fid']['column'] .')
1027              FROM {'. $db_info['table'] .'}
1028              WHERE '. $db_info['columns']['fid']['column'] .' = %d AND nid = %d', $file->fid, $file->delete_nid
1029          ));
1030          $references = $references - $node_references;
1031        }
1032        else {
1033          $references = $references - 1;
1034        }
1035      }
1036    }
1037  
1038    return $references;
1039  }
1040  
1041  /**
1042   * Get a list of node IDs that reference a file.
1043   *
1044   * @param $file
1045   *   The file object for which to find references.
1046   * @param $field
1047   *   Optional. The CCK field array or field name as a string.
1048   * @return
1049   *   An array of IDs grouped by NID: array([nid] => array([vid1], [vid2])).
1050   */
1051  function filefield_get_file_references($file, $field = NULL) {
1052    $fields = filefield_get_field_list(NULL, $field);
1053    $file = (object) $file;
1054  
1055    $references = array();
1056    foreach ($fields as $field) {
1057      $db_info = content_database_info($field);
1058      $sql = 'SELECT nid, vid FROM {'. $db_info['table'] .'} WHERE '. $db_info['columns']['fid']['column'] .' = %d';
1059      $result = db_query($sql, $file->fid);
1060      while ($row = db_fetch_object($result)) {
1061        $references[$row->nid][$row->vid] = $row->vid;
1062      }
1063    }
1064  
1065    return $references;
1066  }
1067  
1068  /**
1069   * Get all FileField files connected to a node ID.
1070   *
1071   * @param $nid
1072   *   The node object.
1073   * @param $field_name
1074   *   Optional. The CCK field array or field name as a string.
1075   * @return
1076   *   An array of all files attached to that field (or all fields).
1077   */
1078  function filefield_get_node_files($node, $field = NULL) {
1079    $fields = filefield_get_field_list($node->type, $field);
1080    $files = array();
1081  
1082    // Get the file rows.
1083    foreach ($fields as $field) {
1084      $db_info = content_database_info($field);
1085      $fields = 'f.*';
1086      $fields .= ', c.'. $db_info['columns']['list']['column'] .' AS list';
1087      $fields .= ', c.'. $db_info['columns']['data']['column'] .' AS data';
1088      $sql =  'SELECT '. $fields .' FROM {files} f INNER JOIN {' . $db_info['table'] . '} c ON f.fid = c.' . $db_info['columns']['fid']['column'] . ' AND c.vid = %d';
1089      $result = db_query($sql, $node->vid);
1090      while ($file = db_fetch_array($result)) {
1091        $file['data'] = unserialize($file['data']);
1092        $files[$file['fid']] = $file;
1093      }
1094    }
1095  
1096    return $files;
1097  }
1098  
1099  /**
1100   * Delete all node references of a file.
1101   *
1102   * @param $file
1103   *   The file object for which to find references.
1104   * @param $field
1105   *   Optional. The CCK field array or field name as a string.
1106   */
1107  function filefield_delete_file_references($file, $field = NULL) {
1108    $fields = filefield_get_field_list(NULL, $field);
1109    $file = (object) $file;
1110  
1111    $references = filefield_get_file_references($file, $field);
1112    foreach ($references as $nid => $node_references) {
1113      // Do not update a node if it is already being deleted directly by the user.
1114      if (isset($file->delete_nid) && $file->delete_nid == $nid) {
1115        continue;
1116      }
1117  
1118      foreach ($node_references as $vid) {
1119        // Do not update the node revision if that revision is already being
1120        // saved or deleted directly by the user.
1121        if (isset($file->delete_vid) && $file->delete_vid == $vid) {
1122          continue;
1123        }
1124  
1125        $node = node_load(array('vid' => $vid));
1126        foreach ($fields as $field_name => $field) {
1127          if (isset($node->$field_name)) {
1128            foreach ($node->$field_name as $delta => $item) {
1129              if ($item['fid'] == $file->fid) {
1130                unset($node->{$field_name}[$delta]);
1131              }
1132            }
1133            $node->$field_name = array_values(array_filter($node->$field_name));
1134          }
1135        }
1136  
1137        // Save the node after removing the file references. This flag prevents
1138        // FileField from attempting to delete the file again.
1139        $node->skip_filefield_delete = TRUE;
1140        node_save($node);
1141      }
1142    }
1143  }


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7