cms/drupal/modules/file/tests/file.test
changeset 541 e756a8c72c3d
child 570 cdf0cb7bf073
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Tests for file.module.
       
     6  */
       
     7 
       
     8 /**
       
     9  * Provides methods specifically for testing File module's field handling.
       
    10  */
       
    11 class FileFieldTestCase extends DrupalWebTestCase {
       
    12   protected $admin_user;
       
    13 
       
    14   function setUp() {
       
    15     // Since this is a base class for many test cases, support the same
       
    16     // flexibility that DrupalWebTestCase::setUp() has for the modules to be
       
    17     // passed in as either an array or a variable number of string arguments.
       
    18     $modules = func_get_args();
       
    19     if (isset($modules[0]) && is_array($modules[0])) {
       
    20       $modules = $modules[0];
       
    21     }
       
    22     $modules[] = 'file';
       
    23     $modules[] = 'file_module_test';
       
    24     parent::setUp($modules);
       
    25     $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'bypass node access', 'administer fields'));
       
    26     $this->drupalLogin($this->admin_user);
       
    27   }
       
    28 
       
    29   /**
       
    30    * Retrieves a sample file of the specified type.
       
    31    */
       
    32   function getTestFile($type_name, $size = NULL) {
       
    33     // Get a file to upload.
       
    34     $file = current($this->drupalGetTestFiles($type_name, $size));
       
    35 
       
    36     // Add a filesize property to files as would be read by file_load().
       
    37     $file->filesize = filesize($file->uri);
       
    38 
       
    39     return $file;
       
    40   }
       
    41 
       
    42   /**
       
    43    * Retrieves the fid of the last inserted file.
       
    44    */
       
    45   function getLastFileId() {
       
    46     return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
       
    47   }
       
    48 
       
    49   /**
       
    50    * Creates a new file field.
       
    51    *
       
    52    * @param $name
       
    53    *   The name of the new field (all lowercase), exclude the "field_" prefix.
       
    54    * @param $type_name
       
    55    *   The node type that this field will be added to.
       
    56    * @param $field_settings
       
    57    *   A list of field settings that will be added to the defaults.
       
    58    * @param $instance_settings
       
    59    *   A list of instance settings that will be added to the instance defaults.
       
    60    * @param $widget_settings
       
    61    *   A list of widget settings that will be added to the widget defaults.
       
    62    */
       
    63   function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) {
       
    64     $field = array(
       
    65       'field_name' => $name,
       
    66       'type' => 'file',
       
    67       'settings' => array(),
       
    68       'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1,
       
    69     );
       
    70     $field['settings'] = array_merge($field['settings'], $field_settings);
       
    71     field_create_field($field);
       
    72 
       
    73     $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings);
       
    74   }
       
    75 
       
    76   /**
       
    77    * Attaches a file field to an entity.
       
    78    *
       
    79    * @param $name
       
    80    *   The name of the new field (all lowercase), exclude the "field_" prefix.
       
    81    * @param $entity_type
       
    82    *   The entity type this field will be added to.
       
    83    * @param $bundle
       
    84    *   The bundle this field will be added to.
       
    85    * @param $field_settings
       
    86    *   A list of field settings that will be added to the defaults.
       
    87    * @param $instance_settings
       
    88    *   A list of instance settings that will be added to the instance defaults.
       
    89    * @param $widget_settings
       
    90    *   A list of widget settings that will be added to the widget defaults.
       
    91    */
       
    92   function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) {
       
    93     $instance = array(
       
    94       'field_name' => $name,
       
    95       'label' => $name,
       
    96       'entity_type' => $entity_type,
       
    97       'bundle' => $bundle,
       
    98       'required' => !empty($instance_settings['required']),
       
    99       'settings' => array(),
       
   100       'widget' => array(
       
   101         'type' => 'file_generic',
       
   102         'settings' => array(),
       
   103       ),
       
   104     );
       
   105     $instance['settings'] = array_merge($instance['settings'], $instance_settings);
       
   106     $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);
       
   107     field_create_instance($instance);
       
   108   }
       
   109 
       
   110   /**
       
   111    * Updates an existing file field with new settings.
       
   112    */
       
   113   function updateFileField($name, $type_name, $instance_settings = array(), $widget_settings = array()) {
       
   114     $instance = field_info_instance('node', $name, $type_name);
       
   115     $instance['settings'] = array_merge($instance['settings'], $instance_settings);
       
   116     $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);
       
   117 
       
   118     field_update_instance($instance);
       
   119   }
       
   120 
       
   121   /**
       
   122    * Uploads a file to a node.
       
   123    */
       
   124   function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE, $extras = array()) {
       
   125     $langcode = LANGUAGE_NONE;
       
   126     $edit = array(
       
   127       "title" => $this->randomName(),
       
   128       'revision' => (string) (int) $new_revision,
       
   129     );
       
   130 
       
   131     if (is_numeric($nid_or_type)) {
       
   132       $nid = $nid_or_type;
       
   133     }
       
   134     else {
       
   135       // Add a new node.
       
   136       $extras['type'] = $nid_or_type;
       
   137       $node = $this->drupalCreateNode($extras);
       
   138       $nid = $node->nid;
       
   139       // Save at least one revision to better simulate a real site.
       
   140       $this->drupalCreateNode(get_object_vars($node));
       
   141       $node = node_load($nid, NULL, TRUE);
       
   142       $this->assertNotEqual($nid, $node->vid, 'Node revision exists.');
       
   143     }
       
   144 
       
   145     // Attach a file to the node.
       
   146     $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($file->uri);
       
   147     $this->drupalPost("node/$nid/edit", $edit, t('Save'));
       
   148 
       
   149     return $nid;
       
   150   }
       
   151 
       
   152   /**
       
   153    * Removes a file from a node.
       
   154    *
       
   155    * Note that if replacing a file, it must first be removed then added again.
       
   156    */
       
   157   function removeNodeFile($nid, $new_revision = TRUE) {
       
   158     $edit = array(
       
   159       'revision' => (string) (int) $new_revision,
       
   160     );
       
   161 
       
   162     $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
       
   163     $this->drupalPost(NULL, $edit, t('Save'));
       
   164   }
       
   165 
       
   166   /**
       
   167    * Replaces a file within a node.
       
   168    */
       
   169   function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
       
   170     $edit = array(
       
   171       'files[' . $field_name . '_' . LANGUAGE_NONE . '_0]' => drupal_realpath($file->uri),
       
   172       'revision' => (string) (int) $new_revision,
       
   173     );
       
   174 
       
   175     $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
       
   176     $this->drupalPost(NULL, $edit, t('Save'));
       
   177   }
       
   178 
       
   179   /**
       
   180    * Asserts that a file exists physically on disk.
       
   181    */
       
   182   function assertFileExists($file, $message = NULL) {
       
   183     $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri));
       
   184     $this->assertTrue(is_file($file->uri), $message);
       
   185   }
       
   186 
       
   187   /**
       
   188    * Asserts that a file exists in the database.
       
   189    */
       
   190   function assertFileEntryExists($file, $message = NULL) {
       
   191     entity_get_controller('file')->resetCache();
       
   192     $db_file = file_load($file->fid);
       
   193     $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri));
       
   194     $this->assertEqual($db_file->uri, $file->uri, $message);
       
   195   }
       
   196 
       
   197   /**
       
   198    * Asserts that a file does not exist on disk.
       
   199    */
       
   200   function assertFileNotExists($file, $message = NULL) {
       
   201     $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri));
       
   202     $this->assertFalse(is_file($file->uri), $message);
       
   203   }
       
   204 
       
   205   /**
       
   206    * Asserts that a file does not exist in the database.
       
   207    */
       
   208   function assertFileEntryNotExists($file, $message) {
       
   209     entity_get_controller('file')->resetCache();
       
   210     $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri));
       
   211     $this->assertFalse(file_load($file->fid), $message);
       
   212   }
       
   213 
       
   214   /**
       
   215    * Asserts that a file's status is set to permanent in the database.
       
   216    */
       
   217   function assertFileIsPermanent($file, $message = NULL) {
       
   218     $message = isset($message) ? $message : format_string('File %file is permanent.', array('%file' => $file->uri));
       
   219     $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message);
       
   220   }
       
   221 
       
   222   /**
       
   223    * Creates a temporary file, for a specific user.
       
   224    *
       
   225    * @param string $data
       
   226    *   A string containing the contents of the file.
       
   227    * @param int $uid
       
   228    *   The user ID of the file owner.
       
   229    *
       
   230    * @return object
       
   231    *   A file object, or FALSE on error.
       
   232    */
       
   233   function createTemporaryFile($data, $uid = NULL) {
       
   234     $file = file_save_data($data, NULL, NULL);
       
   235 
       
   236     if ($file) {
       
   237       $file->uid = isset($uid) ? $uid : $this->admin_user->uid;
       
   238       // Change the file status to be temporary.
       
   239       $file->status = NULL;
       
   240       return file_save($file);
       
   241     }
       
   242 
       
   243     return $file;
       
   244   }
       
   245 }
       
   246 
       
   247 /**
       
   248  * Tests adding a file to a non-node entity.
       
   249  */
       
   250 class FileTaxonomyTermTestCase extends DrupalWebTestCase {
       
   251   protected $admin_user;
       
   252 
       
   253   public static function getInfo() {
       
   254     return array(
       
   255       'name' => 'Taxonomy term file test',
       
   256       'description' => 'Tests adding a file to a non-node entity.',
       
   257       'group' => 'File',
       
   258     );
       
   259   }
       
   260 
       
   261   public function setUp() {
       
   262     $modules[] = 'file';
       
   263     $modules[] = 'taxonomy';
       
   264     parent::setUp($modules);
       
   265     $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer taxonomy'));
       
   266     $this->drupalLogin($this->admin_user);
       
   267   }
       
   268 
       
   269   /**
       
   270    * Creates a file field and attaches it to the "Tags" taxonomy vocabulary.
       
   271    *
       
   272    * @param $name
       
   273    *   The field name of the file field to create.
       
   274    * @param $uri_scheme
       
   275    *   The URI scheme to use for the file field (for example, "private" to
       
   276    *   create a field that stores private files or "public" to create a field
       
   277    *   that stores public files).
       
   278    */
       
   279   protected function createAttachFileField($name, $uri_scheme) {
       
   280     $field = array(
       
   281       'field_name' => $name,
       
   282       'type' => 'file',
       
   283       'settings' => array(
       
   284         'uri_scheme' => $uri_scheme,
       
   285       ),
       
   286       'cardinality' => 1,
       
   287     );
       
   288     field_create_field($field);
       
   289     // Attach an instance of it.
       
   290     $instance = array(
       
   291       'field_name' => $name,
       
   292       'label' => 'File',
       
   293       'entity_type' => 'taxonomy_term',
       
   294       'bundle' => 'tags',
       
   295       'required' => FALSE,
       
   296       'settings' => array(),
       
   297       'widget' => array(
       
   298         'type' => 'file_generic',
       
   299         'settings' => array(),
       
   300       ),
       
   301     );
       
   302     field_create_instance($instance);
       
   303   }
       
   304 
       
   305   /**
       
   306    * Tests that a public file can be attached to a taxonomy term.
       
   307    *
       
   308    * This is a regression test for https://www.drupal.org/node/2305017.
       
   309    */
       
   310   public function testTermFilePublic() {
       
   311     $this->_testTermFile('public');
       
   312   }
       
   313 
       
   314   /**
       
   315    * Tests that a private file can be attached to a taxonomy term.
       
   316    *
       
   317    * This is a regression test for https://www.drupal.org/node/2305017.
       
   318    */
       
   319   public function testTermFilePrivate() {
       
   320     $this->_testTermFile('private');
       
   321   }
       
   322 
       
   323   /**
       
   324    * Runs tests for attaching a file field to a taxonomy term.
       
   325    *
       
   326    * @param $uri_scheme
       
   327    *   The URI scheme to use for the file field, either "public" or "private".
       
   328    */
       
   329   protected function _testTermFile($uri_scheme) {
       
   330     $field_name = strtolower($this->randomName());
       
   331     $this->createAttachFileField($field_name, $uri_scheme);
       
   332     // Get a file to upload.
       
   333     $file = current($this->drupalGetTestFiles('text'));
       
   334     // Add a filesize property to files as would be read by file_load().
       
   335     $file->filesize = filesize($file->uri);
       
   336     $langcode = LANGUAGE_NONE;
       
   337     $edit = array(
       
   338       "name" => $this->randomName(),
       
   339     );
       
   340     // Attach a file to the term.
       
   341     $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($file->uri);
       
   342     $this->drupalPost("admin/structure/taxonomy/tags/add", $edit, t('Save'));
       
   343     // Find the term ID we just created.
       
   344     $tid = db_query_range('SELECT tid FROM {taxonomy_term_data} ORDER BY tid DESC', 0, 1)->fetchField();
       
   345     $terms = entity_load('taxonomy_term', array($tid));
       
   346     $term = $terms[$tid];
       
   347     $fid = $term->{$field_name}[LANGUAGE_NONE][0]['fid'];
       
   348     // Check that the uploaded file is present on the edit form.
       
   349     $this->drupalGet("taxonomy/term/$tid/edit");
       
   350     $file_input_name = $field_name . '[' . LANGUAGE_NONE . '][0][fid]';
       
   351     $this->assertFieldByXpath('//input[@type="hidden" and @name="' . $file_input_name . '"]', $fid, 'File is attached on edit form.');
       
   352     // Edit the term and change name without changing the file.
       
   353     $edit = array(
       
   354       "name" => $this->randomName(),
       
   355     );
       
   356     $this->drupalPost("taxonomy/term/$tid/edit", $edit, t('Save'));
       
   357     // Check that the uploaded file is still present on the edit form.
       
   358     $this->drupalGet("taxonomy/term/$tid/edit");
       
   359     $file_input_name = $field_name . '[' . LANGUAGE_NONE . '][0][fid]';
       
   360     $this->assertFieldByXpath('//input[@type="hidden" and @name="' . $file_input_name . '"]', $fid, 'File is attached on edit form.');
       
   361     // Load term while resetting the cache.
       
   362     $terms = entity_load('taxonomy_term', array($tid), array(), TRUE);
       
   363     $term = $terms[$tid];
       
   364     $this->assertTrue(!empty($term->{$field_name}[LANGUAGE_NONE]), 'Term has attached files.');
       
   365     $this->assertEqual($term->{$field_name}[LANGUAGE_NONE][0]['fid'], $fid, 'Same File ID is attached to the term.');
       
   366   }
       
   367 }
       
   368 
       
   369 /**
       
   370  * Tests the 'managed_file' element type.
       
   371  *
       
   372  * @todo Create a FileTestCase base class and move FileFieldTestCase methods
       
   373  *   that aren't related to fields into it.
       
   374  */
       
   375 class FileManagedFileElementTestCase extends FileFieldTestCase {
       
   376   public static function getInfo() {
       
   377     return array(
       
   378       'name' => 'Managed file element test',
       
   379       'description' => 'Tests the managed_file element type.',
       
   380       'group' => 'File',
       
   381     );
       
   382   }
       
   383 
       
   384   /**
       
   385    * Tests the managed_file element type.
       
   386    */
       
   387   function testManagedFile() {
       
   388     // Check that $element['#size'] is passed to the child upload element.
       
   389     $this->drupalGet('file/test');
       
   390     $this->assertFieldByXpath('//input[@name="files[nested_file]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.');
       
   391 
       
   392     // Perform the tests with all permutations of $form['#tree'] and
       
   393     // $element['#extended'].
       
   394     foreach (array(0, 1) as $tree) {
       
   395       foreach (array(0, 1) as $extended) {
       
   396         $test_file = $this->getTestFile('text');
       
   397         $path = 'file/test/' . $tree . '/' . $extended;
       
   398         $input_base_name = $tree ? 'nested_file' : 'file';
       
   399 
       
   400         // Submit without a file.
       
   401         $this->drupalPost($path, array(), t('Save'));
       
   402         $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submitted without a file.');
       
   403 
       
   404         // Submit with a file, but with an invalid form token. Ensure the file
       
   405         // was not saved.
       
   406         $last_fid_prior = $this->getLastFileId();
       
   407         $edit = array(
       
   408           'files[' . $input_base_name . ']' => drupal_realpath($test_file->uri),
       
   409           'form_token' => 'invalid token',
       
   410         );
       
   411         $this->drupalPost($path, $edit, t('Save'));
       
   412         $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
       
   413         $last_fid = $this->getLastFileId();
       
   414         $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
       
   415 
       
   416         // Submit a new file, without using the Upload button.
       
   417         $last_fid_prior = $this->getLastFileId();
       
   418         $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri));
       
   419         $this->drupalPost($path, $edit, t('Save'));
       
   420         $last_fid = $this->getLastFileId();
       
   421         $this->assertTrue($last_fid > $last_fid_prior, 'New file got saved.');
       
   422         $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), 'Submit handler has correct file info.');
       
   423 
       
   424         // Submit no new input, but with a default file.
       
   425         $this->drupalPost($path . '/' . $last_fid, array(), t('Save'));
       
   426         $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), 'Empty submission did not change an existing file.');
       
   427 
       
   428         // Now, test the Upload and Remove buttons, with and without Ajax.
       
   429         foreach (array(FALSE, TRUE) as $ajax) {
       
   430           // Upload, then Submit.
       
   431           $last_fid_prior = $this->getLastFileId();
       
   432           $this->drupalGet($path);
       
   433           $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri));
       
   434           if ($ajax) {
       
   435             $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button');
       
   436           }
       
   437           else {
       
   438             $this->drupalPost(NULL, $edit, t('Upload'));
       
   439           }
       
   440           $last_fid = $this->getLastFileId();
       
   441           $this->assertTrue($last_fid > $last_fid_prior, 'New file got uploaded.');
       
   442           $this->drupalPost(NULL, array(), t('Save'));
       
   443           $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), 'Submit handler has correct file info.');
       
   444 
       
   445           // Remove, then Submit.
       
   446           $this->drupalGet($path . '/' . $last_fid);
       
   447           if ($ajax) {
       
   448             $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button');
       
   449           }
       
   450           else {
       
   451             $this->drupalPost(NULL, array(), t('Remove'));
       
   452           }
       
   453           $this->drupalPost(NULL, array(), t('Save'));
       
   454           $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submission after file removal was successful.');
       
   455 
       
   456           // Upload, then Remove, then Submit.
       
   457           $this->drupalGet($path);
       
   458           $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri));
       
   459           if ($ajax) {
       
   460             $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button');
       
   461             $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button');
       
   462           }
       
   463           else {
       
   464             $this->drupalPost(NULL, $edit, t('Upload'));
       
   465             $this->drupalPost(NULL, array(), t('Remove'));
       
   466           }
       
   467           $this->drupalPost(NULL, array(), t('Save'));
       
   468           $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submission after file upload and removal was successful.');
       
   469         }
       
   470       }
       
   471     }
       
   472   }
       
   473 }
       
   474 
       
   475 /**
       
   476  * Tests file field widget.
       
   477  */
       
   478 class FileFieldWidgetTestCase extends FileFieldTestCase {
       
   479   public static function getInfo() {
       
   480     return array(
       
   481       'name' => 'File field widget test',
       
   482       'description' => 'Tests the file field widget, single and multi-valued, with and without AJAX, with public and private files.',
       
   483       'group' => 'File',
       
   484     );
       
   485   }
       
   486 
       
   487   /**
       
   488    * Tests upload and remove buttons for a single-valued File field.
       
   489    */
       
   490   function testSingleValuedWidget() {
       
   491     // Use 'page' instead of 'article', so that the 'article' image field does
       
   492     // not conflict with this test. If in the future the 'page' type gets its
       
   493     // own default file or image field, this test can be made more robust by
       
   494     // using a custom node type.
       
   495     $type_name = 'page';
       
   496     $field_name = strtolower($this->randomName());
       
   497     $this->createFileField($field_name, $type_name);
       
   498     $field = field_info_field($field_name);
       
   499     $instance = field_info_instance('node', $field_name, $type_name);
       
   500 
       
   501     $test_file = $this->getTestFile('text');
       
   502 
       
   503     foreach (array('nojs', 'js') as $type) {
       
   504       // Create a new node with the uploaded file and ensure it got uploaded
       
   505       // successfully.
       
   506       // @todo This only tests a 'nojs' submission, because drupalPostAJAX()
       
   507       //   does not yet support file uploads.
       
   508       $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
   509       $node = node_load($nid, NULL, TRUE);
       
   510       $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
   511       $this->assertFileExists($node_file, 'New file saved to disk on node creation.');
       
   512 
       
   513       // Test that running field_attach_update() leaves the file intact.
       
   514       $field = new stdClass();
       
   515       $field->type = $type_name;
       
   516       $field->nid = $nid;
       
   517       field_attach_update('node', $field);
       
   518       $node = node_load($nid);
       
   519       $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
   520       $this->assertFileExists($node_file, 'New file still saved to disk on field update.');
       
   521 
       
   522       // Ensure the file can be downloaded.
       
   523       $this->drupalGet(file_create_url($node_file->uri));
       
   524       $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
       
   525 
       
   526       // Ensure the edit page has a remove button instead of an upload button.
       
   527       $this->drupalGet("node/$nid/edit");
       
   528       $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), 'Node with file does not display the "Upload" button.');
       
   529       $this->assertFieldByXpath('//input[@type="submit"]', t('Remove'), 'Node with file displays the "Remove" button.');
       
   530 
       
   531       // "Click" the remove button (emulating either a nojs or js submission).
       
   532       switch ($type) {
       
   533         case 'nojs':
       
   534           $this->drupalPost(NULL, array(), t('Remove'));
       
   535           break;
       
   536         case 'js':
       
   537           $button = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]');
       
   538           $this->drupalPostAJAX(NULL, array(), array((string) $button[0]['name'] => (string) $button[0]['value']));
       
   539           break;
       
   540       }
       
   541 
       
   542       // Ensure the page now has an upload button instead of a remove button.
       
   543       $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'After clicking the "Remove" button, it is no longer displayed.');
       
   544       $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
       
   545 
       
   546       // Save the node and ensure it does not have the file.
       
   547       $this->drupalPost(NULL, array(), t('Save'));
       
   548       $node = node_load($nid, NULL, TRUE);
       
   549       $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), 'File was successfully removed from the node.');
       
   550     }
       
   551   }
       
   552 
       
   553   /**
       
   554    * Tests exploiting the temporary file removal of another user using fid.
       
   555    */
       
   556   function testTemporaryFileRemovalExploit() {
       
   557     // Create a victim user.
       
   558     $victim_user = $this->drupalCreateUser();
       
   559 
       
   560     // Create an attacker user.
       
   561     $attacker_user = $this->drupalCreateUser(array(
       
   562       'access content',
       
   563       'create page content',
       
   564       'edit any page content',
       
   565     ));
       
   566 
       
   567     // Log in as the attacker user.
       
   568     $this->drupalLogin($attacker_user);
       
   569 
       
   570     // Perform tests using the newly created users.
       
   571     $this->doTestTemporaryFileRemovalExploit($victim_user->uid, $attacker_user->uid);
       
   572   }
       
   573 
       
   574   /**
       
   575    * Tests exploiting the temporary file removal for anonymous users using fid.
       
   576    */
       
   577   public function testTemporaryFileRemovalExploitAnonymous() {
       
   578     // Set up an anonymous victim user.
       
   579     $victim_uid = 0;
       
   580 
       
   581     // Set up an anonymous attacker user.
       
   582     $attacker_uid = 0;
       
   583 
       
   584     // Set up permissions for anonymous attacker user.
       
   585     user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(
       
   586       'access content' => TRUE,
       
   587       'create page content' => TRUE,
       
   588       'edit any page content' => TRUE,
       
   589     ));
       
   590 
       
   591     // In order to simulate being the anonymous attacker user, we need to log
       
   592     // out here since setUp() has logged in the admin.
       
   593     $this->drupalLogout();
       
   594 
       
   595     // Perform tests using the newly set up users.
       
   596     $this->doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid);
       
   597   }
       
   598 
       
   599   /**
       
   600    * Tests validation with the Upload button.
       
   601    */
       
   602   function testWidgetValidation() {
       
   603     $type_name = 'article';
       
   604     $field_name = strtolower($this->randomName());
       
   605     $this->createFileField($field_name, $type_name);
       
   606     $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt'));
       
   607 
       
   608     foreach (array('nojs', 'js') as $type) {
       
   609       // Create node and prepare files for upload.
       
   610       $node = $this->drupalCreateNode(array('type' => 'article'));
       
   611       $nid = $node->nid;
       
   612       $this->drupalGet("node/$nid/edit");
       
   613       $test_file_text = $this->getTestFile('text');
       
   614       $test_file_image = $this->getTestFile('image');
       
   615       $field = field_info_field($field_name);
       
   616       $name = 'files[' . $field_name . '_' . LANGUAGE_NONE . '_0]';
       
   617 
       
   618       // Upload file with incorrect extension, check for validation error.
       
   619       $edit[$name] = drupal_realpath($test_file_image->uri);
       
   620       switch ($type) {
       
   621         case 'nojs':
       
   622           $this->drupalPost(NULL, $edit, t('Upload'));
       
   623           break;
       
   624 
       
   625         case 'js':
       
   626           $button = $this->xpath('//input[@type="submit" and @value="' . t('Upload') . '"]');
       
   627           $this->drupalPostAJAX(NULL, $edit, array((string) $button[0]['name'] => (string) $button[0]['value']));
       
   628           break;
       
   629       }
       
   630       $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt'));
       
   631       $this->assertRaw($error_message, t('Validation error when file with wrong extension uploaded (JSMode=%type).', array('%type' => $type)));
       
   632 
       
   633       // Upload file with correct extension, check that error message is removed.
       
   634       $edit[$name] = drupal_realpath($test_file_text->uri);
       
   635       switch ($type) {
       
   636         case 'nojs':
       
   637           $this->drupalPost(NULL, $edit, t('Upload'));
       
   638           break;
       
   639 
       
   640         case 'js':
       
   641           $button = $this->xpath('//input[@type="submit" and @value="' . t('Upload') . '"]');
       
   642           $this->drupalPostAJAX(NULL, $edit, array((string) $button[0]['name'] => (string) $button[0]['value']));
       
   643           break;
       
   644       }
       
   645       $this->assertNoRaw($error_message, t('Validation error removed when file with correct extension uploaded (JSMode=%type).', array('%type' => $type)));
       
   646     }
       
   647   }
       
   648 
       
   649   /**
       
   650    * Helper for testing exploiting the temporary file removal using fid.
       
   651    *
       
   652    * @param int $victim_uid
       
   653    *   The victim user ID.
       
   654    * @param int $attacker_uid
       
   655    *   The attacker user ID.
       
   656    */
       
   657   protected function doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid) {
       
   658     // Use 'page' instead of 'article', so that the 'article' image field does
       
   659     // not conflict with this test. If in the future the 'page' type gets its
       
   660     // own default file or image field, this test can be made more robust by
       
   661     // using a custom node type.
       
   662     $type_name = 'page';
       
   663     $field_name = 'test_file_field';
       
   664     $this->createFileField($field_name, $type_name);
       
   665 
       
   666     $test_file = $this->getTestFile('text');
       
   667     foreach (array('nojs', 'js') as $type) {
       
   668       // Create a temporary file owned by the anonymous victim user. This will be
       
   669       // as if they had uploaded the file, but not saved the node they were
       
   670       // editing or creating.
       
   671       $victim_tmp_file = $this->createTemporaryFile('some text', $victim_uid);
       
   672       $victim_tmp_file = file_load($victim_tmp_file->fid);
       
   673       $this->assertTrue($victim_tmp_file->status != FILE_STATUS_PERMANENT, 'New file saved to disk is temporary.');
       
   674       $this->assertFalse(empty($victim_tmp_file->fid), 'New file has a fid');
       
   675       $this->assertEqual($victim_uid, $victim_tmp_file->uid, 'New file belongs to the victim user');
       
   676 
       
   677       // Have attacker create a new node with a different uploaded file and
       
   678       // ensure it got uploaded successfully.
       
   679       // @todo Can we test AJAX? See https://www.drupal.org/node/2538260
       
   680       $edit = array(
       
   681         'title' => $type . '-title',
       
   682       );
       
   683 
       
   684       // Attach a file to a node.
       
   685       $langcode = LANGUAGE_NONE;
       
   686       $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($test_file->uri);
       
   687       $this->drupalPost("node/add/$type_name", $edit, 'Save');
       
   688       $node = $this->drupalGetNodeByTitle($edit['title']);
       
   689       $node_file = file_load($node->{$field_name}[$langcode][0]['fid']);
       
   690       $this->assertFileExists($node_file, 'New file saved to disk on node creation.');
       
   691       $this->assertEqual($attacker_uid, $node_file->uid, 'New file belongs to the attacker.');
       
   692 
       
   693       // Ensure the file can be downloaded.
       
   694       $this->drupalGet(file_create_url($node_file->uri));
       
   695       $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
       
   696 
       
   697       // "Click" the remove button (emulating either a nojs or js submission).
       
   698       // In this POST request, the attacker "guesses" the fid of the victim's
       
   699       // temporary file and uses that to remove this file.
       
   700       $this->drupalGet('node/' . $node->nid . '/edit');
       
   701       switch ($type) {
       
   702         case 'nojs':
       
   703           $this->drupalPost(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), 'Remove');
       
   704           break;
       
   705         case 'js':
       
   706           $button = $this->xpath('//input[@type="submit" and @value="Remove"]');
       
   707           $this->drupalPostAJAX(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), array((string) $button[0]['name'] => (string) $button[0]['value']));
       
   708           break;
       
   709       }
       
   710 
       
   711       // The victim's temporary file should not be removed by the attacker's
       
   712       // POST request.
       
   713       $this->assertFileExists($victim_tmp_file);
       
   714     }
       
   715   }
       
   716 
       
   717   /**
       
   718    * Tests upload and remove buttons for multiple multi-valued File fields.
       
   719    */
       
   720   function testMultiValuedWidget() {
       
   721     // Use 'page' instead of 'article', so that the 'article' image field does
       
   722     // not conflict with this test. If in the future the 'page' type gets its
       
   723     // own default file or image field, this test can be made more robust by
       
   724     // using a custom node type.
       
   725     $type_name = 'page';
       
   726     $field_name = strtolower($this->randomName());
       
   727     $field_name2 = strtolower($this->randomName());
       
   728     $this->createFileField($field_name, $type_name, array('cardinality' => 3));
       
   729     $this->createFileField($field_name2, $type_name, array('cardinality' => 3));
       
   730 
       
   731     $field = field_info_field($field_name);
       
   732     $instance = field_info_instance('node', $field_name, $type_name);
       
   733 
       
   734     $field2 = field_info_field($field_name2);
       
   735     $instance2 = field_info_instance('node', $field_name2, $type_name);
       
   736 
       
   737     $test_file = $this->getTestFile('text');
       
   738 
       
   739     foreach (array('nojs', 'js') as $type) {
       
   740       // Visit the node creation form, and upload 3 files for each field. Since
       
   741       // the field has cardinality of 3, ensure the "Upload" button is displayed
       
   742       // until after the 3rd file, and after that, isn't displayed. Because
       
   743       // SimpleTest triggers the last button with a given name, so upload to the
       
   744       // second field first.
       
   745       // @todo This is only testing a non-Ajax upload, because drupalPostAJAX()
       
   746       //   does not yet emulate jQuery's file upload.
       
   747       //
       
   748       $this->drupalGet("node/add/$type_name");
       
   749       foreach (array($field_name2, $field_name) as $each_field_name) {
       
   750         for ($delta = 0; $delta < 3; $delta++) {
       
   751           $edit = array('files[' . $each_field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']' => drupal_realpath($test_file->uri));
       
   752           // If the Upload button doesn't exist, drupalPost() will automatically
       
   753           // fail with an assertion message.
       
   754           $this->drupalPost(NULL, $edit, t('Upload'));
       
   755         }
       
   756       }
       
   757       $this->assertNoFieldByXpath('//input[@type="submit"]', t('Upload'), 'After uploading 3 files for each field, the "Upload" button is no longer displayed.');
       
   758 
       
   759       $num_expected_remove_buttons = 6;
       
   760 
       
   761       foreach (array($field_name, $field_name2) as $current_field_name) {
       
   762         // How many uploaded files for the current field are remaining.
       
   763         $remaining = 3;
       
   764         // Test clicking each "Remove" button. For extra robustness, test them out
       
   765         // of sequential order. They are 0-indexed, and get renumbered after each
       
   766         // iteration, so array(1, 1, 0) means:
       
   767         // - First remove the 2nd file.
       
   768         // - Then remove what is then the 2nd file (was originally the 3rd file).
       
   769         // - Then remove the first file.
       
   770         foreach (array(1,1,0) as $delta) {
       
   771           // Ensure we have the expected number of Remove buttons, and that they
       
   772           // are numbered sequentially.
       
   773           $buttons = $this->xpath('//input[@type="submit" and @value="Remove"]');
       
   774           $this->assertTrue(is_array($buttons) && count($buttons) === $num_expected_remove_buttons, format_string('There are %n "Remove" buttons displayed (JSMode=%type).', array('%n' => $num_expected_remove_buttons, '%type' => $type)));
       
   775           foreach ($buttons as $i => $button) {
       
   776             $key = $i >= $remaining ? $i - $remaining : $i;
       
   777             $check_field_name = $field_name2;
       
   778             if ($current_field_name == $field_name && $i < $remaining) {
       
   779               $check_field_name = $field_name;
       
   780             }
       
   781 
       
   782             $this->assertIdentical((string) $button['name'], $check_field_name . '_' . LANGUAGE_NONE . '_' . $key. '_remove_button');
       
   783           }
       
   784 
       
   785           // "Click" the remove button (emulating either a nojs or js submission).
       
   786           $button_name = $current_field_name . '_' . LANGUAGE_NONE . '_' . $delta . '_remove_button';
       
   787           switch ($type) {
       
   788             case 'nojs':
       
   789               // drupalPost() takes a $submit parameter that is the value of the
       
   790               // button whose click we want to emulate. Since we have multiple
       
   791               // buttons with the value "Remove", and want to control which one we
       
   792               // use, we change the value of the other ones to something else.
       
   793               // Since non-clicked buttons aren't included in the submitted POST
       
   794               // data, and since drupalPost() will result in $this being updated
       
   795               // with a newly rebuilt form, this doesn't cause problems.
       
   796               foreach ($buttons as $button) {
       
   797                 if ($button['name'] != $button_name) {
       
   798                   $button['value'] = 'DUMMY';
       
   799                 }
       
   800               }
       
   801               $this->drupalPost(NULL, array(), t('Remove'));
       
   802               break;
       
   803             case 'js':
       
   804               // drupalPostAJAX() lets us target the button precisely, so we don't
       
   805               // require the workaround used above for nojs.
       
   806               $this->drupalPostAJAX(NULL, array(), array($button_name => t('Remove')));
       
   807               break;
       
   808           }
       
   809           $num_expected_remove_buttons--;
       
   810           $remaining--;
       
   811 
       
   812           // Ensure an "Upload" button for the current field is displayed with the
       
   813           // correct name.
       
   814           $upload_button_name = $current_field_name . '_' . LANGUAGE_NONE . '_' . $remaining . '_upload_button';
       
   815           $buttons = $this->xpath('//input[@type="submit" and @value="Upload" and @name=:name]', array(':name' => $upload_button_name));
       
   816           $this->assertTrue(is_array($buttons) && count($buttons) == 1, format_string('The upload button is displayed with the correct name (JSMode=%type).', array('%type' => $type)));
       
   817 
       
   818           // Ensure only at most one button per field is displayed.
       
   819           $buttons = $this->xpath('//input[@type="submit" and @value="Upload"]');
       
   820           $expected = $current_field_name == $field_name ? 1 : 2;
       
   821           $this->assertTrue(is_array($buttons) && count($buttons) == $expected, format_string('After removing a file, only one "Upload" button for each possible field is displayed (JSMode=%type).', array('%type' => $type)));
       
   822         }
       
   823       }
       
   824 
       
   825       // Ensure the page now has no Remove buttons.
       
   826       $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), format_string('After removing all files, there is no "Remove" button displayed (JSMode=%type).', array('%type' => $type)));
       
   827 
       
   828       // Save the node and ensure it does not have any files.
       
   829       $this->drupalPost(NULL, array('title' => $this->randomName()), t('Save'));
       
   830       $matches = array();
       
   831       preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
       
   832       $nid = $matches[1];
       
   833       $node = node_load($nid, NULL, TRUE);
       
   834       $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), 'Node was successfully saved without any files.');
       
   835     }
       
   836   }
       
   837 
       
   838   /**
       
   839    * Tests a file field with a "Private files" upload destination setting.
       
   840    */
       
   841   function testPrivateFileSetting() {
       
   842     // Use 'page' instead of 'article', so that the 'article' image field does
       
   843     // not conflict with this test. If in the future the 'page' type gets its
       
   844     // own default file or image field, this test can be made more robust by
       
   845     // using a custom node type.
       
   846     $type_name = 'page';
       
   847     $field_name = strtolower($this->randomName());
       
   848     $this->createFileField($field_name, $type_name);
       
   849     $field = field_info_field($field_name);
       
   850     $instance = field_info_instance('node', $field_name, $type_name);
       
   851 
       
   852     $test_file = $this->getTestFile('text');
       
   853 
       
   854     // Change the field setting to make its files private, and upload a file.
       
   855     $edit = array('field[settings][uri_scheme]' => 'private');
       
   856     $this->drupalPost("admin/structure/types/manage/$type_name/fields/$field_name", $edit, t('Save settings'));
       
   857     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
   858     $node = node_load($nid, NULL, TRUE);
       
   859     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
   860     $this->assertFileExists($node_file, 'New file saved to disk on node creation.');
       
   861 
       
   862     // Ensure the private file is available to the user who uploaded it.
       
   863     $this->drupalGet(file_create_url($node_file->uri));
       
   864     $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
       
   865 
       
   866     // Ensure we can't change 'uri_scheme' field settings while there are some
       
   867     // entities with uploaded files.
       
   868     $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_name");
       
   869     $this->assertFieldByXpath('//input[@id="edit-field-settings-uri-scheme-public" and @disabled="disabled"]', 'public', 'Upload destination setting disabled.');
       
   870 
       
   871     // Delete node and confirm that setting could be changed.
       
   872     node_delete($nid);
       
   873     $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_name");
       
   874     $this->assertFieldByXpath('//input[@id="edit-field-settings-uri-scheme-public" and not(@disabled)]', 'public', 'Upload destination setting enabled.');
       
   875   }
       
   876 
       
   877   /**
       
   878    * Tests that download restrictions on private files work on comments.
       
   879    */
       
   880   function testPrivateFileComment() {
       
   881     $user = $this->drupalCreateUser(array('access comments'));
       
   882 
       
   883     // Remove access comments permission from anon user.
       
   884     $edit = array(
       
   885       DRUPAL_ANONYMOUS_RID . '[access comments]' => FALSE,
       
   886     );
       
   887     $this->drupalPost('admin/people/permissions', $edit, t('Save permissions'));
       
   888 
       
   889     // Create a new field.
       
   890     $edit = array(
       
   891       'fields[_add_new_field][label]' => $label = $this->randomName(),
       
   892       'fields[_add_new_field][field_name]' => $name = strtolower($this->randomName()),
       
   893       'fields[_add_new_field][type]' => 'file',
       
   894       'fields[_add_new_field][widget_type]' => 'file_generic',
       
   895     );
       
   896     $this->drupalPost('admin/structure/types/manage/article/comment/fields', $edit, t('Save'));
       
   897     $edit = array('field[settings][uri_scheme]' => 'private');
       
   898     $this->drupalPost(NULL, $edit, t('Save field settings'));
       
   899     $this->drupalPost(NULL, array(), t('Save settings'));
       
   900 
       
   901     // Create node.
       
   902     $text_file = $this->getTestFile('text');
       
   903     $edit = array(
       
   904       'title' => $this->randomName(),
       
   905     );
       
   906     $this->drupalPost('node/add/article', $edit, t('Save'));
       
   907     $node = $this->drupalGetNodeByTitle($edit['title']);
       
   908 
       
   909     // Add a comment with a file.
       
   910     $text_file = $this->getTestFile('text');
       
   911     $edit = array(
       
   912       'files[field_' . $name . '_' . LANGUAGE_NONE . '_' . 0 . ']' => drupal_realpath($text_file->uri),
       
   913       'comment_body[' . LANGUAGE_NONE . '][0][value]' => $comment_body = $this->randomName(),
       
   914     );
       
   915     $this->drupalPost(NULL, $edit, t('Save'));
       
   916 
       
   917     // Get the comment ID.
       
   918     preg_match('/comment-([0-9]+)/', $this->getUrl(), $matches);
       
   919     $cid = $matches[1];
       
   920 
       
   921     // Log in as normal user.
       
   922     $this->drupalLogin($user);
       
   923 
       
   924     $comment = comment_load($cid);
       
   925     $comment_file = (object) $comment->{'field_' . $name}[LANGUAGE_NONE][0];
       
   926     $this->assertFileExists($comment_file, 'New file saved to disk on node creation.');
       
   927     // Test authenticated file download.
       
   928     $url = file_create_url($comment_file->uri);
       
   929     $this->assertNotEqual($url, NULL, 'Confirmed that the URL is valid');
       
   930     $this->drupalGet(file_create_url($comment_file->uri));
       
   931     $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
       
   932 
       
   933     // Test anonymous file download.
       
   934     $this->drupalLogout();
       
   935     $this->drupalGet(file_create_url($comment_file->uri));
       
   936     $this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.');
       
   937 
       
   938     // Unpublishes node.
       
   939     $this->drupalLogin($this->admin_user);
       
   940     $edit = array(
       
   941       'status' => FALSE,
       
   942     );
       
   943     $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
       
   944 
       
   945     // Ensures normal user can no longer download the file.
       
   946     $this->drupalLogin($user);
       
   947     $this->drupalGet(file_create_url($comment_file->uri));
       
   948     $this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.');
       
   949   }
       
   950 
       
   951 }
       
   952 
       
   953 /**
       
   954  * Tests file handling with node revisions.
       
   955  */
       
   956 class FileFieldRevisionTestCase extends FileFieldTestCase {
       
   957   public static function getInfo() {
       
   958     return array(
       
   959       'name' => 'File field revision test',
       
   960       'description' => 'Test creating and deleting revisions with files attached.',
       
   961       'group' => 'File',
       
   962     );
       
   963   }
       
   964 
       
   965   /**
       
   966    * Tests creating multiple revisions of a node and managing attached files.
       
   967    *
       
   968    * Expected behaviors:
       
   969    *  - Adding a new revision will make another entry in the field table, but
       
   970    *    the original file will not be duplicated.
       
   971    *  - Deleting a revision should not delete the original file if the file
       
   972    *    is in use by another revision.
       
   973    *  - When the last revision that uses a file is deleted, the original file
       
   974    *    should be deleted also.
       
   975    */
       
   976   function testRevisions() {
       
   977     $type_name = 'article';
       
   978     $field_name = strtolower($this->randomName());
       
   979     $this->createFileField($field_name, $type_name);
       
   980     $field = field_info_field($field_name);
       
   981     $instance = field_info_instance('node', $field_name, $type_name);
       
   982 
       
   983     // Attach the same fields to users.
       
   984     $this->attachFileField($field_name, 'user', 'user');
       
   985 
       
   986     $test_file = $this->getTestFile('text');
       
   987 
       
   988     // Create a new node with the uploaded file.
       
   989     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
   990 
       
   991     // Check that the file exists on disk and in the database.
       
   992     $node = node_load($nid, NULL, TRUE);
       
   993     $node_file_r1 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
   994     $node_vid_r1 = $node->vid;
       
   995     $this->assertFileExists($node_file_r1, 'New file saved to disk on node creation.');
       
   996     $this->assertFileEntryExists($node_file_r1, 'File entry exists in database on node creation.');
       
   997     $this->assertFileIsPermanent($node_file_r1, 'File is permanent.');
       
   998 
       
   999     // Upload another file to the same node in a new revision.
       
  1000     $this->replaceNodeFile($test_file, $field_name, $nid);
       
  1001     $node = node_load($nid, NULL, TRUE);
       
  1002     $node_file_r2 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1003     $node_vid_r2 = $node->vid;
       
  1004     $this->assertFileExists($node_file_r2, 'Replacement file exists on disk after creating new revision.');
       
  1005     $this->assertFileEntryExists($node_file_r2, 'Replacement file entry exists in database after creating new revision.');
       
  1006     $this->assertFileIsPermanent($node_file_r2, 'Replacement file is permanent.');
       
  1007 
       
  1008     // Check that the original file is still in place on the first revision.
       
  1009     $node = node_load($nid, $node_vid_r1, TRUE);
       
  1010     $this->assertEqual($node_file_r1, (object) $node->{$field_name}[LANGUAGE_NONE][0], 'Original file still in place after replacing file in new revision.');
       
  1011     $this->assertFileExists($node_file_r1, 'Original file still in place after replacing file in new revision.');
       
  1012     $this->assertFileEntryExists($node_file_r1, 'Original file entry still in place after replacing file in new revision');
       
  1013     $this->assertFileIsPermanent($node_file_r1, 'Original file is still permanent.');
       
  1014 
       
  1015     // Save a new version of the node without any changes.
       
  1016     // Check that the file is still the same as the previous revision.
       
  1017     $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save'));
       
  1018     $node = node_load($nid, NULL, TRUE);
       
  1019     $node_file_r3 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1020     $node_vid_r3 = $node->vid;
       
  1021     $this->assertEqual($node_file_r2, $node_file_r3, 'Previous revision file still in place after creating a new revision without a new file.');
       
  1022     $this->assertFileIsPermanent($node_file_r3, 'New revision file is permanent.');
       
  1023 
       
  1024     // Revert to the first revision and check that the original file is active.
       
  1025     $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert'));
       
  1026     $node = node_load($nid, NULL, TRUE);
       
  1027     $node_file_r4 = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1028     $node_vid_r4 = $node->vid;
       
  1029     $this->assertEqual($node_file_r1, $node_file_r4, 'Original revision file still in place after reverting to the original revision.');
       
  1030     $this->assertFileIsPermanent($node_file_r4, 'Original revision file still permanent after reverting to the original revision.');
       
  1031 
       
  1032     // Delete the second revision and check that the file is kept (since it is
       
  1033     // still being used by the third revision).
       
  1034     $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete'));
       
  1035     $this->assertFileExists($node_file_r3, 'Second file is still available after deleting second revision, since it is being used by the third revision.');
       
  1036     $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting second revision, since it is being used by the third revision.');
       
  1037     $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting second revision, since it is being used by the third revision.');
       
  1038 
       
  1039     // Attach the second file to a user.
       
  1040     $user = $this->drupalCreateUser();
       
  1041     $edit = (array) $user;
       
  1042     $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3;
       
  1043     user_save($user, $edit);
       
  1044     $this->drupalGet('user/' . $user->uid . '/edit');
       
  1045 
       
  1046     // Delete the third revision and check that the file is not deleted yet.
       
  1047     $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete'));
       
  1048     $this->assertFileExists($node_file_r3, 'Second file is still available after deleting third revision, since it is being used by the user.');
       
  1049     $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting third revision, since it is being used by the user.');
       
  1050     $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting third revision, since it is being used by the user.');
       
  1051 
       
  1052     // Delete the user and check that the file is also deleted.
       
  1053     user_delete($user->uid);
       
  1054     // TODO: This seems like a bug in File API. Clearing the stat cache should
       
  1055     // not be necessary here. The file really is deleted, but stream wrappers
       
  1056     // doesn't seem to think so unless we clear the PHP file stat() cache.
       
  1057     clearstatcache();
       
  1058     $this->assertFileNotExists($node_file_r3, 'Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.');
       
  1059     $this->assertFileEntryNotExists($node_file_r3, 'Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.');
       
  1060 
       
  1061     // Delete the entire node and check that the original file is deleted.
       
  1062     $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete'));
       
  1063     $this->assertFileNotExists($node_file_r1, 'Original file is deleted after deleting the entire node with two revisions remaining.');
       
  1064     $this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.');
       
  1065   }
       
  1066 }
       
  1067 
       
  1068 /**
       
  1069  * Tests that formatters are working properly.
       
  1070  */
       
  1071 class FileFieldDisplayTestCase extends FileFieldTestCase {
       
  1072   public static function getInfo() {
       
  1073     return array(
       
  1074       'name' => 'File field display tests',
       
  1075       'description' => 'Test the display of file fields in node and views.',
       
  1076       'group' => 'File',
       
  1077     );
       
  1078   }
       
  1079 
       
  1080   /**
       
  1081    * Tests normal formatter display on node display.
       
  1082    */
       
  1083   function testNodeDisplay() {
       
  1084     $field_name = strtolower($this->randomName());
       
  1085     $type_name = 'article';
       
  1086     $field_settings = array(
       
  1087       'display_field' => '1',
       
  1088       'display_default' => '1',
       
  1089       'cardinality' => FIELD_CARDINALITY_UNLIMITED,
       
  1090     );
       
  1091     $instance_settings = array(
       
  1092       'description_field' => '1',
       
  1093     );
       
  1094     $widget_settings = array();
       
  1095     $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings);
       
  1096     $field = field_info_field($field_name);
       
  1097     $instance = field_info_instance('node', $field_name, $type_name);
       
  1098 
       
  1099     // Create a new node *without* the file field set, and check that the field
       
  1100     // is not shown for each node display.
       
  1101     $node = $this->drupalCreateNode(array('type' => $type_name));
       
  1102     $file_formatters = array('file_default', 'file_table', 'file_url_plain', 'hidden');
       
  1103     foreach ($file_formatters as $formatter) {
       
  1104       $edit = array(
       
  1105         "fields[$field_name][type]" => $formatter,
       
  1106       );
       
  1107       $this->drupalPost("admin/structure/types/manage/$type_name/display", $edit, t('Save'));
       
  1108       $this->drupalGet('node/' . $node->nid);
       
  1109       $this->assertNoText($field_name, format_string('Field label is hidden when no file attached for formatter %formatter', array('%formatter' => $formatter)));
       
  1110     }
       
  1111 
       
  1112     $test_file = $this->getTestFile('text');
       
  1113 
       
  1114     // Create a new node with the uploaded file.
       
  1115     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1116     $this->drupalGet('node/' . $nid . '/edit');
       
  1117 
       
  1118     // Check that the default formatter is displaying with the file name.
       
  1119     $node = node_load($nid, NULL, TRUE);
       
  1120     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1121     $default_output = theme('file_link', array('file' => $node_file));
       
  1122     $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.');
       
  1123 
       
  1124     // Turn the "display" option off and check that the file is no longer displayed.
       
  1125     $edit = array($field_name . '[' . LANGUAGE_NONE . '][0][display]' => FALSE);
       
  1126     $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));
       
  1127 
       
  1128     $this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.');
       
  1129 
       
  1130     // Test that fields appear as expected during the preview.
       
  1131     // Add a second file.
       
  1132     $name = 'files[' . $field_name . '_' . LANGUAGE_NONE . '_1]';
       
  1133     $edit[$name] = drupal_realpath($test_file->uri);
       
  1134 
       
  1135     // Uncheck the display checkboxes and go to the preview.
       
  1136     $edit[$field_name . '[' . LANGUAGE_NONE . '][0][display]'] = FALSE;
       
  1137     $edit[$field_name . '[' . LANGUAGE_NONE . '][1][display]'] = FALSE;
       
  1138     $this->drupalPost('node/' . $nid . '/edit', $edit, t('Preview'));
       
  1139     $this->assertRaw($field_name . '[' . LANGUAGE_NONE . '][0][display]', 'First file appears as expected.');
       
  1140     $this->assertRaw($field_name . '[' . LANGUAGE_NONE . '][1][display]', 'Second file appears as expected.');
       
  1141   }
       
  1142 
       
  1143   /**
       
  1144    * Tests default display of File Field.
       
  1145    */
       
  1146   function testDefaultFileFieldDisplay() {
       
  1147     $field_name = strtolower($this->randomName());
       
  1148     $type_name = 'article';
       
  1149     $field_settings = array(
       
  1150       'display_field' => '1',
       
  1151       'display_default' => '0',
       
  1152     );
       
  1153     $instance_settings = array(
       
  1154       'description_field' => '1',
       
  1155     );
       
  1156     $widget_settings = array();
       
  1157     $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings);
       
  1158     $field = field_info_field($field_name);
       
  1159     $instance = field_info_instance('node', $field_name, $type_name);
       
  1160 
       
  1161     $test_file = $this->getTestFile('text');
       
  1162 
       
  1163     // Create a new node with the uploaded file.
       
  1164     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1165 
       
  1166     $this->drupalGet('node/' . $nid . '/edit');
       
  1167     $this->assertFieldByXPath('//input[@type="checkbox" and @name="' . $field_name . '[und][0][display]"]', NULL, 'Default file display checkbox field exists.');
       
  1168     $this->assertFieldByXPath('//input[@type="checkbox" and @name="' . $field_name . '[und][0][display]" and not(@checked)]', NULL, 'Default file display is off.');
       
  1169   }
       
  1170 }
       
  1171 
       
  1172 /**
       
  1173  * Tests various validations.
       
  1174  */
       
  1175 class FileFieldValidateTestCase extends FileFieldTestCase {
       
  1176   protected $field;
       
  1177   protected $node_type;
       
  1178 
       
  1179   public static function getInfo() {
       
  1180     return array(
       
  1181       'name' => 'File field validation tests',
       
  1182       'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.',
       
  1183       'group' => 'File',
       
  1184     );
       
  1185   }
       
  1186 
       
  1187   /**
       
  1188    * Tests the required property on file fields.
       
  1189    */
       
  1190   function testRequired() {
       
  1191     $type_name = 'article';
       
  1192     $field_name = strtolower($this->randomName());
       
  1193     $this->createFileField($field_name, $type_name, array(), array('required' => '1'));
       
  1194     $field = field_info_field($field_name);
       
  1195     $instance = field_info_instance('node', $field_name, $type_name);
       
  1196 
       
  1197     $test_file = $this->getTestFile('text');
       
  1198 
       
  1199     // Try to post a new node without uploading a file.
       
  1200     $langcode = LANGUAGE_NONE;
       
  1201     $edit = array("title" => $this->randomName());
       
  1202     $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
       
  1203     $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required file field was empty.');
       
  1204 
       
  1205     // Create a new node with the uploaded file.
       
  1206     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1207     $this->assertTrue($nid !== FALSE, format_string('uploadNodeFile(@test_file, @field_name, @type_name) succeeded', array('@test_file' => $test_file->uri, '@field_name' => $field_name, '@type_name' => $type_name)));
       
  1208 
       
  1209     $node = node_load($nid, NULL, TRUE);
       
  1210 
       
  1211     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1212     $this->assertFileExists($node_file, 'File exists after uploading to the required field.');
       
  1213     $this->assertFileEntryExists($node_file, 'File entry exists after uploading to the required field.');
       
  1214 
       
  1215     // Try again with a multiple value field.
       
  1216     field_delete_field($field_name);
       
  1217     $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1'));
       
  1218 
       
  1219     // Try to post a new node without uploading a file in the multivalue field.
       
  1220     $edit = array('title' => $this->randomName());
       
  1221     $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
       
  1222     $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required multiple value file field was empty.');
       
  1223 
       
  1224     // Create a new node with the uploaded file into the multivalue field.
       
  1225     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1226     $node = node_load($nid, NULL, TRUE);
       
  1227     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1228     $this->assertFileExists($node_file, 'File exists after uploading to the required multiple value field.');
       
  1229     $this->assertFileEntryExists($node_file, 'File entry exists after uploading to the required multipel value field.');
       
  1230 
       
  1231     // Remove our file field.
       
  1232     field_delete_field($field_name);
       
  1233   }
       
  1234 
       
  1235   /**
       
  1236    * Tests the max file size validator.
       
  1237    */
       
  1238   function testFileMaxSize() {
       
  1239     $type_name = 'article';
       
  1240     $field_name = strtolower($this->randomName());
       
  1241     $this->createFileField($field_name, $type_name, array(), array('required' => '1'));
       
  1242     $field = field_info_field($field_name);
       
  1243     $instance = field_info_instance('node', $field_name, $type_name);
       
  1244 
       
  1245     $small_file = $this->getTestFile('text', 131072); // 128KB.
       
  1246     $large_file = $this->getTestFile('text', 1310720); // 1.2MB
       
  1247 
       
  1248     // Test uploading both a large and small file with different increments.
       
  1249     $sizes = array(
       
  1250       '1M' => 1048576,
       
  1251       '1024K' => 1048576,
       
  1252       '1048576' => 1048576,
       
  1253     );
       
  1254 
       
  1255     foreach ($sizes as $max_filesize => $file_limit) {
       
  1256       // Set the max file upload size.
       
  1257       $this->updateFileField($field_name, $type_name, array('max_filesize' => $max_filesize));
       
  1258       $instance = field_info_instance('node', $field_name, $type_name);
       
  1259 
       
  1260       // Create a new node with the small file, which should pass.
       
  1261       $nid = $this->uploadNodeFile($small_file, $field_name, $type_name);
       
  1262       $node = node_load($nid, NULL, TRUE);
       
  1263       $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1264       $this->assertFileExists($node_file, format_string('File exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize)));
       
  1265       $this->assertFileEntryExists($node_file, format_string('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize)));
       
  1266 
       
  1267       // Check that uploading the large file fails (1M limit).
       
  1268       $nid = $this->uploadNodeFile($large_file, $field_name, $type_name);
       
  1269       $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)));
       
  1270       $this->assertRaw($error_message, format_string('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_filesize)));
       
  1271     }
       
  1272 
       
  1273     // Turn off the max filesize.
       
  1274     $this->updateFileField($field_name, $type_name, array('max_filesize' => ''));
       
  1275 
       
  1276     // Upload the big file successfully.
       
  1277     $nid = $this->uploadNodeFile($large_file, $field_name, $type_name);
       
  1278     $node = node_load($nid, NULL, TRUE);
       
  1279     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1280     $this->assertFileExists($node_file, format_string('File exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));
       
  1281     $this->assertFileEntryExists($node_file, format_string('File entry exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));
       
  1282 
       
  1283     // Remove our file field.
       
  1284     field_delete_field($field_name);
       
  1285   }
       
  1286 
       
  1287   /**
       
  1288    * Tests file extension checking.
       
  1289    */
       
  1290   function testFileExtension() {
       
  1291     $type_name = 'article';
       
  1292     $field_name = strtolower($this->randomName());
       
  1293     $this->createFileField($field_name, $type_name);
       
  1294     $field = field_info_field($field_name);
       
  1295     $instance = field_info_instance('node', $field_name, $type_name);
       
  1296 
       
  1297     $test_file = $this->getTestFile('image');
       
  1298     list(, $test_file_extension) = explode('.', $test_file->filename);
       
  1299 
       
  1300     // Disable extension checking.
       
  1301     $this->updateFileField($field_name, $type_name, array('file_extensions' => ''));
       
  1302 
       
  1303     // Check that the file can be uploaded with no extension checking.
       
  1304     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1305     $node = node_load($nid, NULL, TRUE);
       
  1306     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1307     $this->assertFileExists($node_file, 'File exists after uploading a file with no extension checking.');
       
  1308     $this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with no extension checking.');
       
  1309 
       
  1310     // Enable extension checking for text files.
       
  1311     $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt'));
       
  1312 
       
  1313     // Check that the file with the wrong extension cannot be uploaded.
       
  1314     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1315     $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt'));
       
  1316     $this->assertRaw($error_message, 'Node save failed when file uploaded with the wrong extension.');
       
  1317 
       
  1318     // Enable extension checking for text and image files.
       
  1319     $this->updateFileField($field_name, $type_name, array('file_extensions' => "txt $test_file_extension"));
       
  1320 
       
  1321     // Check that the file can be uploaded with extension checking.
       
  1322     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1323     $node = node_load($nid, NULL, TRUE);
       
  1324     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1325     $this->assertFileExists($node_file, 'File exists after uploading a file with extension checking.');
       
  1326     $this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with extension checking.');
       
  1327 
       
  1328     // Remove our file field.
       
  1329     field_delete_field($field_name);
       
  1330   }
       
  1331 }
       
  1332 
       
  1333 /**
       
  1334  * Tests that files are uploaded to proper locations.
       
  1335  */
       
  1336 class FileFieldPathTestCase extends FileFieldTestCase {
       
  1337   public static function getInfo() {
       
  1338     return array(
       
  1339       'name' => 'File field file path tests',
       
  1340       'description' => 'Test that files are uploaded to the proper location with token support.',
       
  1341       'group' => 'File',
       
  1342     );
       
  1343   }
       
  1344 
       
  1345   /**
       
  1346    * Tests the normal formatter display on node display.
       
  1347    */
       
  1348   function testUploadPath() {
       
  1349     $field_name = strtolower($this->randomName());
       
  1350     $type_name = 'article';
       
  1351     $field = $this->createFileField($field_name, $type_name);
       
  1352     $test_file = $this->getTestFile('text');
       
  1353 
       
  1354     // Create a new node.
       
  1355     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1356 
       
  1357     // Check that the file was uploaded to the file root.
       
  1358     $node = node_load($nid, NULL, TRUE);
       
  1359     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1360     $this->assertPathMatch('public://' . $test_file->filename, $node_file->uri, format_string('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri)));
       
  1361 
       
  1362     // Change the path to contain multiple subdirectories.
       
  1363     $field = $this->updateFileField($field_name, $type_name, array('file_directory' => 'foo/bar/baz'));
       
  1364 
       
  1365     // Upload a new file into the subdirectories.
       
  1366     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1367 
       
  1368     // Check that the file was uploaded into the subdirectory.
       
  1369     $node = node_load($nid, NULL, TRUE);
       
  1370     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1371     $this->assertPathMatch('public://foo/bar/baz/' . $test_file->filename, $node_file->uri, format_string('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri)));
       
  1372 
       
  1373     // Check the path when used with tokens.
       
  1374     // Change the path to contain multiple token directories.
       
  1375     $field = $this->updateFileField($field_name, $type_name, array('file_directory' => '[current-user:uid]/[current-user:name]'));
       
  1376 
       
  1377     // Upload a new file into the token subdirectories.
       
  1378     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1379 
       
  1380     // Check that the file was uploaded into the subdirectory.
       
  1381     $node = node_load($nid, NULL, TRUE);
       
  1382     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1383     // Do token replacement using the same user which uploaded the file, not
       
  1384     // the user running the test case.
       
  1385     $data = array('user' => $this->admin_user);
       
  1386     $subdirectory = token_replace('[user:uid]/[user:name]', $data);
       
  1387     $this->assertPathMatch('public://' . $subdirectory . '/' . $test_file->filename, $node_file->uri, format_string('The file %file was uploaded to the correct path with token replacements.', array('%file' => $node_file->uri)));
       
  1388   }
       
  1389 
       
  1390   /**
       
  1391    * Asserts that a file is uploaded to the right location.
       
  1392    *
       
  1393    * @param $expected_path
       
  1394    *   The location where the file is expected to be uploaded. Duplicate file
       
  1395    *   names to not need to be taken into account.
       
  1396    * @param $actual_path
       
  1397    *   Where the file was actually uploaded.
       
  1398    * @param $message
       
  1399    *   The message to display with this assertion.
       
  1400    */
       
  1401   function assertPathMatch($expected_path, $actual_path, $message) {
       
  1402     // Strip off the extension of the expected path to allow for _0, _1, etc.
       
  1403     // suffixes when the file hits a duplicate name.
       
  1404     $pos = strrpos($expected_path, '.');
       
  1405     $base_path = substr($expected_path, 0, $pos);
       
  1406     $extension = substr($expected_path, $pos + 1);
       
  1407 
       
  1408     $result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path);
       
  1409     $this->assertTrue($result, $message);
       
  1410   }
       
  1411 }
       
  1412 
       
  1413 /**
       
  1414  * Tests the file token replacement in strings.
       
  1415  */
       
  1416 class FileTokenReplaceTestCase extends FileFieldTestCase {
       
  1417   public static function getInfo() {
       
  1418     return array(
       
  1419       'name' => 'File token replacement',
       
  1420       'description' => 'Generates text using placeholders for dummy content to check file token replacement.',
       
  1421       'group' => 'File',
       
  1422     );
       
  1423   }
       
  1424 
       
  1425   /**
       
  1426    * Creates a file, then tests the tokens generated from it.
       
  1427    */
       
  1428   function testFileTokenReplacement() {
       
  1429     global $language;
       
  1430     $url_options = array(
       
  1431       'absolute' => TRUE,
       
  1432       'language' => $language,
       
  1433     );
       
  1434 
       
  1435     // Create file field.
       
  1436     $type_name = 'article';
       
  1437     $field_name = 'field_' . strtolower($this->randomName());
       
  1438     $this->createFileField($field_name, $type_name);
       
  1439     $field = field_info_field($field_name);
       
  1440     $instance = field_info_instance('node', $field_name, $type_name);
       
  1441 
       
  1442     $test_file = $this->getTestFile('text');
       
  1443     // Coping a file to test uploads with non-latin filenames.
       
  1444     $filename = drupal_dirname($test_file->uri) . '/текстовый файл.txt';
       
  1445     $test_file = file_copy($test_file, $filename);
       
  1446 
       
  1447     // Create a new node with the uploaded file.
       
  1448     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
       
  1449 
       
  1450     // Load the node and the file.
       
  1451     $node = node_load($nid, NULL, TRUE);
       
  1452     $file = file_load($node->{$field_name}[LANGUAGE_NONE][0]['fid']);
       
  1453 
       
  1454     // Generate and test sanitized tokens.
       
  1455     $tests = array();
       
  1456     $tests['[file:fid]'] = $file->fid;
       
  1457     $tests['[file:name]'] = check_plain($file->filename);
       
  1458     $tests['[file:path]'] = check_plain($file->uri);
       
  1459     $tests['[file:mime]'] = check_plain($file->filemime);
       
  1460     $tests['[file:size]'] = format_size($file->filesize);
       
  1461     $tests['[file:url]'] = check_plain(file_create_url($file->uri));
       
  1462     $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language->language);
       
  1463     $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language->language);
       
  1464     $tests['[file:owner]'] = check_plain(format_username($this->admin_user));
       
  1465     $tests['[file:owner:uid]'] = $file->uid;
       
  1466 
       
  1467     // Test to make sure that we generated something for each token.
       
  1468     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
       
  1469 
       
  1470     foreach ($tests as $input => $expected) {
       
  1471       $output = token_replace($input, array('file' => $file), array('language' => $language));
       
  1472       $this->assertEqual($output, $expected, format_string('Sanitized file token %token replaced.', array('%token' => $input)));
       
  1473     }
       
  1474 
       
  1475     // Generate and test unsanitized tokens.
       
  1476     $tests['[file:name]'] = $file->filename;
       
  1477     $tests['[file:path]'] = $file->uri;
       
  1478     $tests['[file:mime]'] = $file->filemime;
       
  1479     $tests['[file:size]'] = format_size($file->filesize);
       
  1480 
       
  1481     foreach ($tests as $input => $expected) {
       
  1482       $output = token_replace($input, array('file' => $file), array('language' => $language, 'sanitize' => FALSE));
       
  1483       $this->assertEqual($output, $expected, format_string('Unsanitized file token %token replaced.', array('%token' => $input)));
       
  1484     }
       
  1485   }
       
  1486 }
       
  1487 
       
  1488 /**
       
  1489  * Tests file access on private nodes.
       
  1490  */
       
  1491 class FilePrivateTestCase extends FileFieldTestCase {
       
  1492   public static function getInfo() {
       
  1493     return array(
       
  1494       'name' => 'Private file test',
       
  1495       'description' => 'Uploads a test to a private node and checks access.',
       
  1496       'group' => 'File',
       
  1497     );
       
  1498   }
       
  1499 
       
  1500   function setUp() {
       
  1501     parent::setUp(array('node_access_test', 'field_test'));
       
  1502     node_access_rebuild();
       
  1503     variable_set('node_access_test_private', TRUE);
       
  1504   }
       
  1505 
       
  1506   /**
       
  1507    * Tests file access for file uploaded to a private node.
       
  1508    */
       
  1509   function testPrivateFile() {
       
  1510     // Use 'page' instead of 'article', so that the 'article' image field does
       
  1511     // not conflict with this test. If in the future the 'page' type gets its
       
  1512     // own default file or image field, this test can be made more robust by
       
  1513     // using a custom node type.
       
  1514     $type_name = 'page';
       
  1515     $field_name = strtolower($this->randomName());
       
  1516     $this->createFileField($field_name, $type_name, array('uri_scheme' => 'private'));
       
  1517 
       
  1518     // Create a field with no view access - see field_test_field_access().
       
  1519     $no_access_field_name = 'field_no_view_access';
       
  1520     $this->createFileField($no_access_field_name, $type_name, array('uri_scheme' => 'private'));
       
  1521 
       
  1522     $test_file = $this->getTestFile('text');
       
  1523     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name, TRUE, array('private' => TRUE));
       
  1524     $node = node_load($nid, NULL, TRUE);
       
  1525     $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
       
  1526     // Ensure the file can be downloaded.
       
  1527     $this->drupalGet(file_create_url($node_file->uri));
       
  1528     $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
       
  1529     $this->drupalLogOut();
       
  1530     $this->drupalGet(file_create_url($node_file->uri));
       
  1531     $this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.');
       
  1532 
       
  1533     // Test with the field that should deny access through field access.
       
  1534     $this->drupalLogin($this->admin_user);
       
  1535     $nid = $this->uploadNodeFile($test_file, $no_access_field_name, $type_name, TRUE, array('private' => TRUE));
       
  1536     $node = node_load($nid, NULL, TRUE);
       
  1537     $node_file = (object) $node->{$no_access_field_name}[LANGUAGE_NONE][0];
       
  1538     // Ensure the file cannot be downloaded.
       
  1539     $this->drupalGet(file_create_url($node_file->uri));
       
  1540     $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission.');
       
  1541 
       
  1542     // Attempt to reuse the existing file when creating a new node, and confirm
       
  1543     // that access is still denied.
       
  1544     $edit = array();
       
  1545     $edit['title'] = $this->randomName(8);
       
  1546     $edit[$field_name . '[' . LANGUAGE_NONE . '][0][fid]'] = $node_file->fid;
       
  1547     $this->drupalPost('node/add/page', $edit, t('Save'));
       
  1548     $new_node = $this->drupalGetNodeByTitle($edit['title']);
       
  1549     $this->assertTrue(!empty($new_node), 'Node was created.');
       
  1550     $this->assertUrl('node/' . $new_node->nid);
       
  1551     $this->assertNoRaw($node_file->filename, 'File without view field access permission does not appear after attempting to attach it to a new node.');
       
  1552     $this->drupalGet(file_create_url($node_file->uri));
       
  1553     $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission after attempting to attach it to a new node.');
       
  1554 
       
  1555     // As an anonymous user, create a temporary file with no references and
       
  1556     // confirm that only the session that uploaded it may view it.
       
  1557     $this->drupalLogout();
       
  1558     user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array(
       
  1559       "create $type_name content",
       
  1560       'access content',
       
  1561     ));
       
  1562     $test_file = $this->getTestFile('text');
       
  1563     $this->drupalGet('node/add/' . $type_name);
       
  1564     $edit = array('files[' . $field_name . '_' . LANGUAGE_NONE . '_0]' => drupal_realpath($test_file->uri));
       
  1565     $this->drupalPost(NULL, $edit, t('Upload'));
       
  1566     $files = file_load_multiple(array(), array('uid' => 0));
       
  1567     $this->assertEqual(1, count($files), 'Loaded one anonymous file.');
       
  1568     $file = end($files);
       
  1569     $this->assertNotEqual($file->status, FILE_STATUS_PERMANENT, 'File is temporary.');
       
  1570     $usage = file_usage_list($file);
       
  1571     $this->assertFalse($usage, 'No file usage found.');
       
  1572     $file_url = file_create_url($file->uri);
       
  1573     $this->drupalGet($file_url);
       
  1574     $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.');
       
  1575     // Close the prior connection and remove the session cookie.
       
  1576     $this->curlClose();
       
  1577     $this->cookies = array();
       
  1578     $this->drupalGet($file_url);
       
  1579     $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.');
       
  1580 
       
  1581     // As an anonymous user, create a permanent file that is referenced by a
       
  1582     // published node and confirm that all anonymous users may view it.
       
  1583     $test_file = $this->getTestFile('text');
       
  1584     $this->drupalGet('node/add/' . $type_name);
       
  1585     $edit = array();
       
  1586     $edit['title'] = $this->randomName();
       
  1587     $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'] = drupal_realpath($test_file->uri);
       
  1588     $this->drupalPost(NULL, $edit, t('Save'));
       
  1589     $new_node = $this->drupalGetNodeByTitle($edit['title']);
       
  1590     $file = file_load($new_node->{$field_name}[LANGUAGE_NONE][0]['fid']);
       
  1591     $this->assertEqual($file->status, FILE_STATUS_PERMANENT, 'File is permanent.');
       
  1592     $usage = file_usage_list($file);
       
  1593     $this->assertTrue($usage, 'File usage found.');
       
  1594     $file_url = file_create_url($file->uri);
       
  1595     $this->drupalGet($file_url);
       
  1596     $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.');
       
  1597     // Close the prior connection and remove the session cookie.
       
  1598     $this->curlClose();
       
  1599     $this->cookies = array();
       
  1600     $this->drupalGet($file_url);
       
  1601     $this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.');
       
  1602 
       
  1603     // As an anonymous user, create a permanent file that is referenced by an
       
  1604     // unpublished node and confirm that no anonymous users may view it (even
       
  1605     // the session that uploaded the file) because they cannot view the
       
  1606     // unpublished node.
       
  1607     $test_file = $this->getTestFile('text');
       
  1608     $this->drupalGet('node/add/' . $type_name);
       
  1609     $edit = array();
       
  1610     $edit['title'] = $this->randomName();
       
  1611     $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'] = drupal_realpath($test_file->uri);
       
  1612     $this->drupalPost(NULL, $edit, t('Save'));
       
  1613     $new_node = $this->drupalGetNodeByTitle($edit['title']);
       
  1614     $new_node->status = NODE_NOT_PUBLISHED;
       
  1615     node_save($new_node);
       
  1616     $file = file_load($new_node->{$field_name}[LANGUAGE_NONE][0]['fid']);
       
  1617     $this->assertEqual($file->status, FILE_STATUS_PERMANENT, 'File is permanent.');
       
  1618     $usage = file_usage_list($file);
       
  1619     $this->assertTrue($usage, 'File usage found.');
       
  1620     $file_url = file_create_url($file->uri);
       
  1621     $this->drupalGet($file_url);
       
  1622     $this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.');
       
  1623     // Close the prior connection and remove the session cookie.
       
  1624     $this->curlClose();
       
  1625     $this->cookies = array();
       
  1626     $this->drupalGet($file_url);
       
  1627     $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.');
       
  1628   }
       
  1629 }
       
  1630 
       
  1631 /**
       
  1632  * Confirm that file field submissions work correctly for anonymous visitors.
       
  1633  */
       
  1634 class FileFieldAnonymousSubmission extends FileFieldTestCase {
       
  1635 
       
  1636   public static function getInfo() {
       
  1637     return array(
       
  1638       'name' => 'File form anonymous submission',
       
  1639       'description' => 'Test anonymous form submission.',
       
  1640       'group' => 'File',
       
  1641     );
       
  1642   }
       
  1643 
       
  1644   function setUp() {
       
  1645     parent::setUp();
       
  1646 
       
  1647     // Allow node submissions by anonymous users.
       
  1648     user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array(
       
  1649       'create article content',
       
  1650       'access content',
       
  1651     ));
       
  1652   }
       
  1653 
       
  1654   /**
       
  1655    * Tests the basic node submission for an anonymous visitor.
       
  1656    */
       
  1657   function testAnonymousNode() {
       
  1658     $bundle_label = 'Article';
       
  1659     $node_title = 'Test page';
       
  1660 
       
  1661     // Load the node form.
       
  1662     $this->drupalGet('node/add/article');
       
  1663     $this->assertResponse(200, 'Loaded the article node form.');
       
  1664     $this->assertText(strip_tags(t('Create @name', array('@name' => $bundle_label))));
       
  1665 
       
  1666     $edit = array(
       
  1667       'title' => $node_title,
       
  1668       'body[und][0][value]' => 'Test article',
       
  1669       'body[und][0][format]' => 'filtered_html',
       
  1670     );
       
  1671     $this->drupalPost(NULL, $edit, t('Save'));
       
  1672     $this->assertResponse(200);
       
  1673     $t_args = array('@type' => $bundle_label, '%title' => $node_title);
       
  1674     $this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
       
  1675     $matches = array();
       
  1676     if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
       
  1677       $nid = end($matches);
       
  1678       $this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.');
       
  1679       $node = node_load($nid);
       
  1680       $this->assertNotEqual($node, NULL, 'The node was loaded successfully.');
       
  1681     }
       
  1682   }
       
  1683 
       
  1684   /**
       
  1685    * Tests file submission for an anonymous visitor.
       
  1686    */
       
  1687   function testAnonymousNodeWithFile() {
       
  1688     $bundle_label = 'Article';
       
  1689     $node_title = 'Test page';
       
  1690 
       
  1691     // Load the node form.
       
  1692     $this->drupalGet('node/add/article');
       
  1693     $this->assertResponse(200, 'Loaded the article node form.');
       
  1694     $this->assertText(strip_tags(t('Create @name', array('@name' => $bundle_label))));
       
  1695 
       
  1696     // Generate an image file.
       
  1697     $image = $this->getTestImage();
       
  1698 
       
  1699     // Submit the form.
       
  1700     $edit = array(
       
  1701       'title' => $node_title,
       
  1702       'body[und][0][value]' => 'Test article',
       
  1703       'body[und][0][format]' => 'filtered_html',
       
  1704       'files[field_image_und_0]' => drupal_realpath($image->uri),
       
  1705     );
       
  1706     $this->drupalPost(NULL, $edit, t('Save'));
       
  1707     $this->assertResponse(200);
       
  1708     $t_args = array('@type' => $bundle_label, '%title' => $node_title);
       
  1709     $this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
       
  1710     $matches = array();
       
  1711     if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
       
  1712       $nid = end($matches);
       
  1713       $this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.');
       
  1714       $node = node_load($nid);
       
  1715       $this->assertNotEqual($node, NULL, 'The node was loaded successfully.');
       
  1716       $this->assertEqual($node->field_image[LANGUAGE_NONE][0]['filename'], $image->filename, 'The image was uploaded successfully.');
       
  1717     }
       
  1718   }
       
  1719 
       
  1720   /**
       
  1721    * Tests file submission for an anonymous visitor with a missing node title.
       
  1722    */
       
  1723   function testAnonymousNodeWithFileWithoutTitle() {
       
  1724     $this->drupalLogout();
       
  1725     $this->_testNodeWithFileWithoutTitle();
       
  1726   }
       
  1727 
       
  1728   /**
       
  1729    * Tests file submission for an authenticated user with a missing node title.
       
  1730    */
       
  1731   function testAuthenticatedNodeWithFileWithoutTitle() {
       
  1732     $admin_user = $this->drupalCreateUser(array(
       
  1733       'bypass node access',
       
  1734       'access content overview',
       
  1735       'administer nodes',
       
  1736     ));
       
  1737     $this->drupalLogin($admin_user);
       
  1738     $this->_testNodeWithFileWithoutTitle();
       
  1739   }
       
  1740 
       
  1741   /**
       
  1742    * Helper method to test file submissions with missing node titles.
       
  1743    */
       
  1744   protected function _testNodeWithFileWithoutTitle() {
       
  1745     $bundle_label = 'Article';
       
  1746     $node_title = 'Test page';
       
  1747 
       
  1748     // Load the node form.
       
  1749     $this->drupalGet('node/add/article');
       
  1750     $this->assertResponse(200, 'Loaded the article node form.');
       
  1751     $this->assertText(strip_tags(t('Create @name', array('@name' => $bundle_label))));
       
  1752 
       
  1753     // Generate an image file.
       
  1754     $image = $this->getTestImage();
       
  1755 
       
  1756     // Submit the form but exclude the title field.
       
  1757     $edit = array(
       
  1758       'body[und][0][value]' => 'Test article',
       
  1759       'body[und][0][format]' => 'filtered_html',
       
  1760       'files[field_image_und_0]' => drupal_realpath($image->uri),
       
  1761     );
       
  1762     $this->drupalPost(NULL, $edit, t('Save'));
       
  1763     $this->assertResponse(200);
       
  1764     $t_args = array('@type' => $bundle_label, '%title' => $node_title);
       
  1765     $this->assertNoText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
       
  1766     $this->assertText(t('!name field is required.', array('!name' => t('Title'))));
       
  1767 
       
  1768     // Submit the form again but this time with the missing title field. This
       
  1769     // should still work.
       
  1770     $edit = array(
       
  1771       'title' => $node_title,
       
  1772     );
       
  1773     $this->drupalPost(NULL, $edit, t('Save'));
       
  1774 
       
  1775     // Confirm the final submission actually worked.
       
  1776     $t_args = array('@type' => $bundle_label, '%title' => $node_title);
       
  1777     $this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
       
  1778     $matches = array();
       
  1779     if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
       
  1780       $nid = end($matches);
       
  1781       $this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.');
       
  1782       $node = node_load($nid);
       
  1783       $this->assertNotEqual($node, NULL, 'The node was loaded successfully.');
       
  1784       $this->assertEqual($node->field_image[LANGUAGE_NONE][0]['filename'], $image->filename, 'The image was uploaded successfully.');
       
  1785     }
       
  1786   }
       
  1787 
       
  1788   /**
       
  1789    * Generates a test image.
       
  1790    *
       
  1791    * @return stdClass
       
  1792    *   A file object.
       
  1793    */
       
  1794   function getTestImage() {
       
  1795     // Get a file to upload.
       
  1796     $file = current($this->drupalGetTestFiles('image'));
       
  1797 
       
  1798     // Add a filesize property to files as would be read by file_load().
       
  1799     $file->filesize = filesize($file->uri);
       
  1800 
       
  1801     return $file;
       
  1802   }
       
  1803 
       
  1804 }