3 namespace Drupal\file\Tests;
5 use Drupal\file\Entity\File;
8 * Tests creating and deleting revisions with files attached.
12 class FileFieldRevisionTest extends FileFieldTestBase {
14 * Tests creating multiple revisions of a node and managing attached files.
17 * - Adding a new revision will make another entry in the field table, but
18 * the original file will not be duplicated.
19 * - Deleting a revision should not delete the original file if the file
20 * is in use by another revision.
21 * - When the last revision that uses a file is deleted, the original file
22 * should be deleted also.
24 public function testRevisions() {
25 $node_storage = $this->container->get('entity.manager')->getStorage('node');
26 $type_name = 'article';
27 $field_name = strtolower($this->randomMachineName());
28 $this->createFileField($field_name, 'node', $type_name);
29 // Create the same fields for users.
30 $this->createFileField($field_name, 'user', 'user');
32 $test_file = $this->getTestFile('text');
34 // Create a new node with the uploaded file.
35 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
37 // Check that the file exists on disk and in the database.
38 $node_storage->resetCache([$nid]);
39 $node = $node_storage->load($nid);
40 $node_file_r1 = File::load($node->{$field_name}->target_id);
41 $node_vid_r1 = $node->getRevisionId();
42 $this->assertFileExists($node_file_r1, 'New file saved to disk on node creation.');
43 $this->assertFileEntryExists($node_file_r1, 'File entry exists in database on node creation.');
44 $this->assertFileIsPermanent($node_file_r1, 'File is permanent.');
46 // Upload another file to the same node in a new revision.
47 $this->replaceNodeFile($test_file, $field_name, $nid);
48 $node_storage->resetCache([$nid]);
49 $node = $node_storage->load($nid);
50 $node_file_r2 = File::load($node->{$field_name}->target_id);
51 $node_vid_r2 = $node->getRevisionId();
52 $this->assertFileExists($node_file_r2, 'Replacement file exists on disk after creating new revision.');
53 $this->assertFileEntryExists($node_file_r2, 'Replacement file entry exists in database after creating new revision.');
54 $this->assertFileIsPermanent($node_file_r2, 'Replacement file is permanent.');
56 // Check that the original file is still in place on the first revision.
57 $node = node_revision_load($node_vid_r1);
58 $current_file = File::load($node->{$field_name}->target_id);
59 $this->assertEqual($node_file_r1->id(), $current_file->id(), 'Original file still in place after replacing file in new revision.');
60 $this->assertFileExists($node_file_r1, 'Original file still in place after replacing file in new revision.');
61 $this->assertFileEntryExists($node_file_r1, 'Original file entry still in place after replacing file in new revision');
62 $this->assertFileIsPermanent($node_file_r1, 'Original file is still permanent.');
64 // Save a new version of the node without any changes.
65 // Check that the file is still the same as the previous revision.
66 $this->drupalPostForm('node/' . $nid . '/edit', ['revision' => '1'], t('Save and keep published'));
67 $node_storage->resetCache([$nid]);
68 $node = $node_storage->load($nid);
69 $node_file_r3 = File::load($node->{$field_name}->target_id);
70 $node_vid_r3 = $node->getRevisionId();
71 $this->assertEqual($node_file_r2->id(), $node_file_r3->id(), 'Previous revision file still in place after creating a new revision without a new file.');
72 $this->assertFileIsPermanent($node_file_r3, 'New revision file is permanent.');
74 // Revert to the first revision and check that the original file is active.
75 $this->drupalPostForm('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', [], t('Revert'));
76 $node_storage->resetCache([$nid]);
77 $node = $node_storage->load($nid);
78 $node_file_r4 = File::load($node->{$field_name}->target_id);
79 $this->assertEqual($node_file_r1->id(), $node_file_r4->id(), 'Original revision file still in place after reverting to the original revision.');
80 $this->assertFileIsPermanent($node_file_r4, 'Original revision file still permanent after reverting to the original revision.');
82 // Delete the second revision and check that the file is kept (since it is
83 // still being used by the third revision).
84 $this->drupalPostForm('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', [], t('Delete'));
85 $this->assertFileExists($node_file_r3, 'Second file is still available after deleting second revision, since it is being used by the third revision.');
86 $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting second revision, since it is being used by the third revision.');
87 $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting second revision, since it is being used by the third revision.');
89 // Attach the second file to a user.
90 $user = $this->drupalCreateUser();
91 $user->$field_name->target_id = $node_file_r3->id();
92 $user->$field_name->display = 1;
94 $this->drupalGet('user/' . $user->id() . '/edit');
96 // Delete the third revision and check that the file is not deleted yet.
97 $this->drupalPostForm('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', [], t('Delete'));
98 $this->assertFileExists($node_file_r3, 'Second file is still available after deleting third revision, since it is being used by the user.');
99 $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting third revision, since it is being used by the user.');
100 $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting third revision, since it is being used by the user.');
102 // Delete the user and check that the file is also deleted.
104 // TODO: This seems like a bug in File API. Clearing the stat cache should
105 // not be necessary here. The file really is deleted, but stream wrappers
106 // doesn't seem to think so unless we clear the PHP file stat() cache.
107 clearstatcache($node_file_r1->getFileUri());
108 clearstatcache($node_file_r2->getFileUri());
109 clearstatcache($node_file_r3->getFileUri());
110 clearstatcache($node_file_r4->getFileUri());
112 // Call file_cron() to clean up the file. Make sure the changed timestamp
113 // of the file is older than the system.file.temporary_maximum_age
114 // configuration value.
115 db_update('file_managed')
117 'changed' => REQUEST_TIME - ($this->config('system.file')->get('temporary_maximum_age') + 1),
119 ->condition('fid', $node_file_r3->id())
121 \Drupal::service('cron')->run();
123 $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.');
124 $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.');
126 // Delete the entire node and check that the original file is deleted.
127 $this->drupalPostForm('node/' . $nid . '/delete', [], t('Delete'));
128 // Call file_cron() to clean up the file. Make sure the changed timestamp
129 // of the file is older than the system.file.temporary_maximum_age
130 // configuration value.
131 db_update('file_managed')
133 'changed' => REQUEST_TIME - ($this->config('system.file')->get('temporary_maximum_age') + 1),
135 ->condition('fid', $node_file_r1->id())
137 \Drupal::service('cron')->run();
138 $this->assertFileNotExists($node_file_r1, 'Original file is deleted after deleting the entire node with two revisions remaining.');
139 $this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.');