file.test 33.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?php
// $Id$

/**
 * @file
 * Tests file uploading through the File module.
 */

/**
 * This class provides methods specifically for testing File's field handling.
 */
class FileFieldTestCase extends DrupalWebTestCase {
  protected $admin_user;

  function setUp() {
    parent::setUp('file');
17
    $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer content types', 'administer nodes', 'bypass node access'));
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
    $this->drupalLogin($this->admin_user);
  }

  /**
   * Get a sample file of the specified type.
   */
  function getTestFile($type_name, $size = NULL) {
    // Get a file to upload.
    $file = current($this->drupalGetTestFiles($type_name, $size));

    // Add a filesize property to files as would be read by file_load().
    $file->filesize = filesize($file->uri);

    return $file;
  }

  /**
   * Create a new file field.
   *
   * @param $name
   *   The name of the new field (all lowercase), exclude the "field_" prefix.
   * @param $type_name
   *   The node type that this field will be added to.
   * @param $field_settings
   *   A list of field settings that will be added to the defaults.
   * @param $instance_settings
   *   A list of instance settings that will be added to the instance defaults.
   * @param $widget_settings
   *   A list of widget settings that will be added to the widget defaults.
   */
  function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) {
    $field = array(
      'field_name' => $name,
      'type' => 'file',
      'settings' => array(),
      'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1,
    );
    $field['settings'] = array_merge($field['settings'], $field_settings);
    field_create_field($field);

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
    $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings);
  }

  /**
   * Attach a file field to an entity.
   *
   * @param $name
   *   The name of the new field (all lowercase), exclude the "field_" prefix.
   * @param $entity_type
   *   The entity type this field will be added to.
   * @param $bundle
   *   The bundle this field will be added to.
   * @param $field_settings
   *   A list of field settings that will be added to the defaults.
   * @param $instance_settings
   *   A list of instance settings that will be added to the instance defaults.
   * @param $widget_settings
   *   A list of widget settings that will be added to the widget defaults.
   */
  function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) {
78
    $instance = array(
79
      'field_name' => $name,
80
      'label' => $name,
81 82
      'entity_type' => $entity_type,
      'bundle' => $bundle,
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
      'required' => !empty($instance_settings['required']),
      'settings' => array(),
      'widget' => array(
        'type' => 'file_generic',
        'settings' => array(),
      ),
    );
    $instance['settings'] = array_merge($instance['settings'], $instance_settings);
    $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);
    field_create_instance($instance);
  }

  /**
   * Update an existing file field with new settings.
   */
  function updateFileField($name, $type_name, $instance_settings = array(), $widget_settings = array()) {
    $field = field_info_field($name);
100
    $instance = field_info_instance('node', $name, $type_name);
101 102 103 104 105 106 107 108 109 110
    $instance['settings'] = array_merge($instance['settings'], $instance_settings);
    $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);

    field_update_instance($instance);
  }

  /**
   * Upload a file to a node.
   */
  function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE) {
111
    $langcode = LANGUAGE_NONE;
112
    $edit = array(
113
      "title" => $this->randomName(),
114 115 116 117 118 119
      'revision' => (string) (int) $new_revision,
    );

    if (is_numeric($nid_or_type)) {
      $node = node_load($nid_or_type);
      $delta = isset($node->$field_name) ? count($node->$field_name) : 0;
120
      $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']'] = realpath($file->uri);
121 122 123
      $this->drupalPost('node/' . $nid_or_type . '/edit', $edit, t('Save'));
    }
    else {
124
      $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'] = realpath($file->uri);
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
      $type_name = str_replace('_', '-', $nid_or_type);
      $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
    }

    $matches = array();
    preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
    return isset($matches[1]) ? $matches[1] : FALSE;
  }

  /**
   * Remove a file from a node.
   *
   * Note that if replacing a file, it must first be removed then added again.
   */
  function removeNodeFile($nid, $new_revision = TRUE) {
    $edit = array(
      'revision' => (string) (int) $new_revision,
    );

    $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
    $this->drupalPost(NULL, $edit, t('Save'));
  }

  /**
   * Replace a file within a node.
   */
  function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
    $edit = array(
153
      'files[' . $field_name . '_' . LANGUAGE_NONE . '_0]' => realpath($file->uri),
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
      'revision' => (string) (int) $new_revision,
    );

    $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
    $this->drupalPost(NULL, $edit, t('Save'));
  }

  /**
   * Assert that a file exists physically on disk.
   */
  function assertFileExists($file, $message = NULL) {
    $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri));
    $this->assertTrue(is_file($file->uri), $message);
  }

  /**
   * Assert that a file exists in the database.
   */
  function assertFileEntryExists($file, $message = NULL) {
    drupal_static_reset('file_load_multiple');
    $db_file = file_load($file->fid);
    $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri));
    $this->assertEqual($db_file->uri, $file->uri, $message);
  }

  /**
   * Assert that a file does not exist on disk.
   */
  function assertFileNotExists($file, $message = NULL) {
    $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri));
    $this->assertFalse(is_file($file->uri), $message);
  }

  /**
   * Assert that a file does not exist in the database.
   */
  function assertFileEntryNotExists($file, $message) {
    drupal_static_reset('file_load_multiple');
    $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri));
    $this->assertFalse(file_load($file->fid), $message);
  }
195 196 197 198 199 200 201 202

  /**
   * Assert that a file's status is set to permanent in the database.
   */
  function assertFileIsPermanent($file, $message = NULL) {
    $message = isset($message) ? $message : t('File %file is permanent.', array('%file' => $file->uri));
    $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message);
  }
203 204
}

205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282

/**
 * Test class to test file field upload and remove buttons, with and without AJAX.
 */
class FileFieldWidgetTestCase extends FileFieldTestCase {
  public static function getInfo() {
    return array(
      'name' => 'File field widget test',
      'description' => 'Test upload and remove buttons, with and without AJAX.',
      'group' => 'File',
    );
  }

  /**
   * Tests upload and remove buttons, with and without AJAX.
   *
   * @todo This function currently only tests the "remove" button of a single-
   *   valued field. Tests should be added for the "upload" button and for each
   *   button of a multi-valued field. Tests involving multiple AJAX steps on
   *   the same page will become easier after http://drupal.org/node/789186
   *   lands. Testing the "upload" button in AJAX context requires more
   *   investigation into how jQuery uploads files, so that drupalPostAJAX() can
   *   emulate that correctly.
   */
  function testWidget() {
    // Use 'page' instead of 'article', so that the 'article' image field does
    // not conflict with this test. If in the future the 'page' type gets its
    // own default file or image field, this test can be made more robust by
    // using a custom node type.
    $type_name = 'page';
    $field_name = strtolower($this->randomName());
    $this->createFileField($field_name, $type_name);
    $field = field_info_field($field_name);
    $instance = field_info_instance('node', $field_name, $type_name);

    $test_file = $this->getTestFile('text');

    foreach (array('nojs', 'js') as $type) {
      // Create a new node with the uploaded file and ensure it got uploaded
      // successfully.
      $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
      $node = node_load($nid, NULL, TRUE);
      $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
      $this->assertFileExists($node_file, t('New file saved to disk on node creation.'));

      // Ensure the edit page has a remove button instead of an upload button.
      $this->drupalGet("node/$nid/edit");
      $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('Node with file does not display the "Upload" button.'));
      $this->assertFieldByXpath('//input[@type="submit"]', t('Remove'), t('Node with file displays the "Remove" button.'));

      // "Click" the remove button (emulating either a nojs or js submission).
      switch ($type) {
        case 'nojs':
          $this->drupalPost(NULL, array(), t('Remove'));
          break;
        case 'js':
          // @todo This can be simplified after http://drupal.org/node/789186
          //   lands.
          preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $this->content, $matches);
          $settings = drupal_json_decode($matches[1]);
          $button = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]');
          $button_id = (string) $button[0]['id'];
          $this->drupalPostAJAX(NULL, array(), array((string) $button[0]['name'] => (string) $button[0]['value']), $settings['ajax'][$button_id]['url'], array(), array(), NULL, $settings['ajax'][$button_id]);
          break;
      }

      // Ensure the page now has an upload button instead of a remove button.
      $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), t('After clicking the "Remove" button, it is no longer displayed.'));
      $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), t('After clicking the "Remove" button, the "Upload" button is displayed.'));

      // Save the node and ensure it does not have the file.
      $this->drupalPost(NULL, array(), t('Save'));
      $node = node_load($nid, NULL, TRUE);
      $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), t('File was successfully removed from the node.'));
    }
  }
}

283 284 285 286
/**
 * Test class to test file handling with node revisions.
 */
class FileFieldRevisionTestCase extends FileFieldTestCase {
287
  public static function getInfo() {
288
    return array(
289 290 291
      'name' => 'File field revision test',
      'description' => 'Test creating and deleting revisions with files attached.',
      'group' => 'File',
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
    );
  }

  /**
   * Test creating multiple revisions of a node and managing the attached files.
   *
   * Expected behaviors:
   *  - Adding a new revision will make another entry in the field table, but
   *    the original file will not be duplicated.
   *  - Deleting a revision should not delete the original file if the file
   *    is in use by another revision.
   *  - When the last revision that uses a file is deleted, the original file
   *    should be deleted also.
   */
  function testRevisions() {
    $type_name = 'article';
308
    $field_name = strtolower($this->randomName());
309 310
    $this->createFileField($field_name, $type_name);
    $field = field_info_field($field_name);
311
    $instance = field_info_instance('node', $field_name, $type_name);
312

313 314 315
    // Attach the same fields to users.
    $this->attachFileField($field_name, 'user', 'user');

316 317 318 319 320 321 322
    $test_file = $this->getTestFile('text');

    // Create a new node with the uploaded file.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file exists on disk and in the database.
    $node = node_load($nid, NULL, TRUE);
323
    $node_file_r1 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
324 325 326
    $node_vid_r1 = $node->vid;
    $this->assertFileExists($node_file_r1, t('New file saved to disk on node creation.'));
    $this->assertFileEntryExists($node_file_r1, t('File entry exists in database on node creation.'));
327
    $this->assertFileIsPermanent($node_file_r1, t('File is permanent.'));
328 329 330 331

    // Upload another file to the same node in a new revision.
    $this->replaceNodeFile($test_file, $field_name, $nid);
    $node = node_load($nid, NULL, TRUE);
332
    $node_file_r2 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
333 334 335
    $node_vid_r2 = $node->vid;
    $this->assertFileExists($node_file_r2, t('Replacement file exists on disk after creating new revision.'));
    $this->assertFileEntryExists($node_file_r2, t('Replacement file entry exists in database after creating new revision.'));
336
    $this->assertFileIsPermanent($node_file_r2, t('Replacement file is permanent.'));
337 338 339

    // Check that the original file is still in place on the first revision.
    $node = node_load($nid, $node_vid_r1, TRUE);
340
    $this->assertEqual($node_file_r1, (object) $node->{$field_name}[LANGUAGE_NONE][0], t('Original file still in place after replacing file in new revision.'));
341 342
    $this->assertFileExists($node_file_r1, t('Original file still in place after replacing file in new revision.'));
    $this->assertFileEntryExists($node_file_r1, t('Original file entry still in place after replacing file in new revision'));
343
    $this->assertFileIsPermanent($node_file_r1, t('Original file is still permanent.'));
344 345 346 347 348

    // Save a new version of the node without any changes.
    // Check that the file is still the same as the previous revision.
    $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save'));
    $node = node_load($nid, NULL, TRUE);
349
    $node_file_r3 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
350 351
    $node_vid_r3 = $node->vid;
    $this->assertEqual($node_file_r2, $node_file_r3, t('Previous revision file still in place after creating a new revision without a new file.'));
352
    $this->assertFileIsPermanent($node_file_r3, t('New revision file is permanent.'));
353 354 355 356

    // Revert to the first revision and check that the original file is active.
    $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert'));
    $node = node_load($nid, NULL, TRUE);
357
    $node_file_r4 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
358 359
    $node_vid_r4 = $node->vid;
    $this->assertEqual($node_file_r1, $node_file_r4, t('Original revision file still in place after reverting to the original revision.'));
360
    $this->assertFileIsPermanent($node_file_r4, t('Original revision file still permanent after reverting to the original revision.'));
361 362 363 364 365 366

    // Delete the second revision and check that the file is kept (since it is
    // still being used by the third revision).
    $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete'));
    $this->assertFileExists($node_file_r3, t('Second file is still available after deleting second revision, since it is being used by the third revision.'));
    $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting second revision, since it is being used by the third revision.'));
367
    $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting second revision, since it is being used by the third revision.'));
368

369 370 371 372 373 374 375 376
    // Attach the second file to a user.
    $user = $this->drupalCreateUser();
    $edit = array();
    $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3;
    user_save($user, $edit);
    $this->drupalGet('user/' . $user->uid . '/edit');

    // Delete the third revision and check that the file is not deleted yet.
377
    $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete'));
378 379 380 381 382 383
    $this->assertFileExists($node_file_r3, t('Second file is still available after deleting third revision, since it is being used by the user.'));
    $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting third revision, since it is being used by the user.'));
    $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting third revision, since it is being used by the user.'));

    // Delete the user and check that the file is also deleted.
    user_delete($user->uid);
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
    // TODO: This seems like a bug in File API. Clearing the stat cache should
    // not be necessary here. The file really is deleted, but stream wrappers
    // doesn't seem to think so unless we clear the PHP file stat() cache.
    clearstatcache();
    $this->assertFileNotExists($node_file_r3, t('Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.'));
    $this->assertFileEntryNotExists($node_file_r3, t('Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.'));

    // Delete the entire node and check that the original file is deleted.
    $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete'));
    $this->assertFileNotExists($node_file_r1, t('Original file is deleted after deleting the entire node with two revisions remaining.'));
    $this->assertFileEntryNotExists($node_file_r1, t('Original file entry is deleted after deleting the entire node with two revisions remaining.'));
  }
}

/**
 * Test class to check that formatters are working properly.
 */
class FileFieldDisplayTestCase extends FileFieldTestCase {
402
  public static function getInfo() {
403
    return array(
404 405 406
      'name' => 'File field display tests',
      'description' => 'Test the display of file fields in node and views.',
      'group' => 'File',
407 408 409 410 411 412 413
    );
  }

  /**
   * Test normal formatter display on node display.
   */
  function testNodeDisplay() {
414
    $field_name = strtolower($this->randomName());
415 416 417 418 419
    $type_name = 'article';
    $field_settings = array(
      'display_field' => '1',
      'display_default' => '1',
    );
420
    $instance_settings = array(
421 422
      'description_field' => '1',
    );
423
    $widget_settings = array();
424 425
    $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings);
    $field = field_info_field($field_name);
426
    $instance = field_info_instance('node', $field_name, $type_name);
427 428 429 430 431 432 433 434 435

    $test_file = $this->getTestFile('text');

    // Create a new node with the uploaded file.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $this->drupalGet('node/' . $nid . '/edit');

    // Check that the default formatter is displaying with the file name.
    $node = node_load($nid, NULL, TRUE);
436
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
437
    $default_output = theme('file_link', array('file' => $node_file));
438 439 440
    $this->assertRaw($default_output, t('Default formatter displaying correctly on full node view.'));

    // Turn the "display" option off and check that the file is no longer displayed.
441
    $edit = array($field_name . '[' . LANGUAGE_NONE . '][0][display]' => FALSE);
442 443 444 445 446 447 448 449 450 451 452 453 454 455
    $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));

    $this->assertNoRaw($default_output, t('Field is hidden when "display" option is unchecked.'));

  }
}

/**
 * Test class to check for various validations.
 */
class FileFieldValidateTestCase extends FileFieldTestCase {
  protected $field;
  protected $node_type;

456
  public static function getInfo() {
457
    return array(
458 459 460
      'name' => 'File field validation tests',
      'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.',
      'group' => 'File',
461 462 463 464 465 466 467 468
    );
  }

  /**
   * Test required property on file fields.
   */
  function testRequired() {
    $type_name = 'article';
469
    $field_name = strtolower($this->randomName());
470 471
    $this->createFileField($field_name, $type_name, array(), array('required' => '1'));
    $field = field_info_field($field_name);
472
    $instance = field_info_instance('node', $field_name, $type_name);
473 474 475 476

    $test_file = $this->getTestFile('text');

    // Try to post a new node without uploading a file.
477
    $langcode = LANGUAGE_NONE;
478
    $edit = array("title" => $this->randomName());
479 480 481 482 483 484 485
    $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
    $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required file field was empty.'));

    // Create a new node with the uploaded file.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);

486
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
487 488 489 490 491 492 493 494
    $this->assertFileExists($node_file, t('File exists after uploading to the required field.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required field.'));

    // Try again with a multiple value field.
    field_delete_field($field_name);
    $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1'));

    // Try to post a new node without uploading a file in the multivalue field.
495
    $edit = array('title' => $this->randomName());
496 497 498 499 500 501
    $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
    $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required multiple value file field was empty.'));

    // Create a new node with the uploaded file into the multivalue field.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
502
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
503 504 505 506 507 508 509 510 511 512 513 514
    $this->assertFileExists($node_file, t('File exists after uploading to the required multiple value field.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required multipel value field.'));

    // Remove our file field.
    field_delete_field($field_name);
  }

  /**
   * Test the max file size validator.
   */
  function testFileMaxSize() {
    $type_name = 'article';
515
    $field_name = strtolower($this->randomName());
516 517
    $this->createFileField($field_name, $type_name, array(), array('required' => '1'));
    $field = field_info_field($field_name);
518
    $instance = field_info_instance('node', $field_name, $type_name);
519 520 521 522 523 524 525 526 527 528 529 530 531 532

    $small_file = $this->getTestFile('text', 131072); // 128KB.
    $large_file = $this->getTestFile('text', 1310720); // 1.2MB

    // Test uploading both a large and small file with different increments.
    $sizes = array(
      '1M' => 1048576,
      '1024K' => 1048576,
      '1048576' => 1048576,
    );

    foreach ($sizes as $max_filesize => $file_limit) {
      // Set the max file upload size.
      $this->updateFileField($field_name, $type_name, array('max_filesize' => $max_filesize));
533
      $instance = field_info_instance('node', $field_name, $type_name);
534 535 536 537

      // Create a new node with the small file, which should pass.
      $nid = $this->uploadNodeFile($small_file, $field_name, $type_name);
      $node = node_load($nid, NULL, TRUE);
538
      $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
      $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize)));
      $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize)));

      // Check that uploading the large file fails (1M limit).
      $nid = $this->uploadNodeFile($large_file, $field_name, $type_name);
      $error_message = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($large_file->filesize), '%maxsize' => format_size($file_limit)));
      $this->assertRaw($error_message, t('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_filesize)));
    }

    // Turn off the max filesize.
    $this->updateFileField($field_name, $type_name, array('max_filesize' => ''));

    // Upload the big file successfully.
    $nid = $this->uploadNodeFile($large_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
554
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
555 556 557 558 559 560 561 562 563 564 565 566
    $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));

    // Remove our file field.
    field_delete_field($field_name);
  }

  /**
   * Test the file extension, do additional checks if mimedetect is installed.
   */
  function testFileExtension() {
    $type_name = 'article';
567
    $field_name = strtolower($this->randomName());
568 569
    $this->createFileField($field_name, $type_name);
    $field = field_info_field($field_name);
570
    $instance = field_info_instance('node', $field_name, $type_name);
571 572

    $test_file = $this->getTestFile('image');
573
    list(, $test_file_extension) = explode('.', $test_file->filename);
574 575 576 577 578 579 580

    // Disable extension checking.
    $this->updateFileField($field_name, $type_name, array('file_extensions' => ''));

    // Check that the file can be uploaded with no extension checking.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
581
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
582 583 584 585 586 587 588 589 590 591 592 593
    $this->assertFileExists($node_file, t('File exists after uploading a file with no extension checking.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with no extension checking.'));

    // Enable extension checking for text files.
    $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt'));

    // Check that the file with the wrong extension cannot be uploaded.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt'));
    $this->assertRaw($error_message, t('Node save failed when file uploaded with the wrong extension.'));

    // Enable extension checking for text and image files.
594
    $this->updateFileField($field_name, $type_name, array('file_extensions' => "txt $test_file_extension"));
595 596 597 598

    // Check that the file can be uploaded with extension checking.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
599
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
600 601 602 603 604 605 606 607 608 609 610 611
    $this->assertFileExists($node_file, t('File exists after uploading a file with extension checking.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with extension checking.'));

    // Remove our file field.
    field_delete_field($field_name);
  }
}

/**
 * Test class to check that files are uploaded to proper locations.
 */
class FileFieldPathTestCase extends FileFieldTestCase {
612
  public static function getInfo() {
613
    return array(
614 615 616
      'name' => 'File field file path tests',
      'description' => 'Test that files are uploaded to the proper location with token support.',
      'group' => 'File',
617 618 619 620 621 622 623
    );
  }

  /**
   * Test normal formatter display on node display.
   */
  function testUploadPath() {
624
    $field_name = strtolower($this->randomName());
625 626 627 628 629 630 631 632 633
    $type_name = 'article';
    $field = $this->createFileField($field_name, $type_name);
    $test_file = $this->getTestFile('text');

    // Create a new node.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file was uploaded to the file root.
    $node = node_load($nid, NULL, TRUE);
634
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
635 636 637 638 639 640 641 642 643 644
    $this->assertPathMatch('public://' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri)));

    // Change the path to contain multiple subdirectories.
    $field = $this->updateFileField($field_name, $type_name, array('file_directory' => 'foo/bar/baz'));

    // Upload a new file into the subdirectories.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file was uploaded into the subdirectory.
    $node = node_load($nid, NULL, TRUE);
645
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
646 647 648 649 650 651 652 653 654 655 656
    $this->assertPathMatch('public://foo/bar/baz/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri)));

    // Check the path when used with tokens.
    // Change the path to contain multiple token directories.
    $field = $this->updateFileField($field_name, $type_name, array('file_directory' => '[user:uid]/[user:name]'));

    // Upload a new file into the token subdirectories.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file was uploaded into the subdirectory.
    $node = node_load($nid, NULL, TRUE);
657
    $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
658 659 660 661 662 663 664
    $data = array('user' => $this->admin_user);
    $subdirectory = token_replace('[user:uid]/[user:name]', $data);
    $this->assertPathMatch('public://' . $subdirectory . '/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path with token replacements.', array('%file' => $node_file->uri)));
  }

  /**
   * A loose assertion to check that a file is uploaded to the right location.
665
   *
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
   * @param $expected_path
   *   The location where the file is expected to be uploaded. Duplicate file
   *   names to not need to be taken into account.
   * @param $actual_path
   *   Where the file was actually uploaded.
   * @param $message
   *   The message to display with this assertion.
   */
  function assertPathMatch($expected_path, $actual_path, $message) {
    // Strip off the extension of the expected path to allow for _0, _1, etc.
    // suffixes when the file hits a duplicate name.
    $pos = strrpos($expected_path, '.');
    $base_path = substr($expected_path, 0, $pos);
    $extension = substr($expected_path, $pos + 1);

    $result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path);
    $this->assertTrue($result, $message);
  }
}
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

/**
 * Test file token replacement in strings.
 */
class FileTokenReplaceTestCase extends FileFieldTestCase {
  public static function getInfo() {
    return array(
      'name' => 'File token replacement',
      'description' => 'Generates text using placeholders for dummy content to check file token replacement.',
      'group' => 'File',
    );
  }

  /**
   * Creates a file, then tests the tokens generated from it.
   */
  function testFileTokenReplacement() {
    global $language;
    $url_options = array(
      'absolute' => TRUE,
      'language' => $language,
    );

    // Create file field.
    $type_name = 'article';
    $field_name = 'field_' . strtolower($this->randomName());
    $this->createFileField($field_name, $type_name);
    $field = field_info_field($field_name);
    $instance = field_info_instance('node', $field_name, $type_name);

    $test_file = $this->getTestFile('text');

    // Create a new node with the uploaded file.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Load the node and the file.
    $node = node_load($nid);
    $file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
    $file->description = 'File description.';

    // Generate and test sanitized tokens.
    $tests = array();
    $tests['[file:fid]'] = $file->fid;
    $tests['[file:name]'] = check_plain($file->filename);
    $tests['[file:description]'] = filter_xss($file->description);
    $tests['[file:path]'] = filter_xss($file->uri);
    $tests['[file:mime]'] = filter_xss($file->filemime);
    $tests['[file:size]'] = format_size($file->filesize);
    $tests['[file:url]'] = url(file_create_url($file->uri), $url_options);
    $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language->language);
    $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language->language);
    $tests['[file:owner]'] = $this->admin_user->name;
737
    $tests['[file:owner:uid]'] = $file->uid;
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759

    // Test to make sure that we generated something for each token.
    $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.'));

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('file' => $file), array('language' => $language));
      $this->assertFalse(strcmp($output, $expected), t('Sanitized file token %token replaced.', array('%token' => $input)));
    }

    // Generate and test unsanitized tokens.
    $tests['[file:name]'] = $file->filename;
    $tests['[file:description]'] = $file->description;
    $tests['[file:path]'] = $file->uri;
    $tests['[file:mime]'] = $file->filemime;
    $tests['[file:size]'] = format_size($file->filesize);

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('file' => $file), array('language' => $language, 'sanitize' => FALSE));
      $this->assertFalse(strcmp($output, $expected), t('Unsanitized file token %token replaced.', array('%token' => $input)));
    }
  }
}