summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/file/tests/src/Functional/FileFieldRevisionTest.php
blob: 13eb5e8e68c2efd85cf19455424b0d2a5072fcbb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php

declare(strict_types=1);

namespace Drupal\Tests\file\Functional;

use Drupal\Core\Database\Database;
use Drupal\file\Entity\File;

/**
 * Tests creating and deleting revisions with files attached.
 *
 * @group file
 */
class FileFieldRevisionTest extends FileFieldTestBase {

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * Tests creating multiple revisions of a node and managing attached files.
   *
   * Expected behaviors:
   *  - Adding a new revision will make another entry in the field table, but
   *    the original file will not be duplicated.
   *  - Deleting a revision should not delete the original file if the file
   *    is in use by another revision.
   *  - When the last revision that uses a file is deleted, the original file
   *    should be deleted also.
   */
  public function testRevisions(): void {
    // This test expects unused managed files to be marked as a temporary file
    // and then deleted up by file_cron().
    $this->config('file.settings')
      ->set('make_unused_managed_files_temporary', TRUE)
      ->save();
    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
    $type_name = 'article';
    $field_name = $this->randomMachineName();
    $this->createFileField($field_name, 'node', $type_name);
    // Create the same fields for users.
    $this->createFileField($field_name, 'user', 'user');

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

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

    // Check that the file exists on disk and in the database.
    $node = $node_storage->load($nid);
    $node_file_r1 = File::load($node->{$field_name}->target_id);
    $node_vid_r1 = $node->getRevisionId();
    $this->assertFileExists($node_file_r1->getFileUri());
    $this->assertFileEntryExists($node_file_r1, 'File entry exists in database on node creation.');
    $this->assertFileIsPermanent($node_file_r1, 'File is permanent.');

    // Upload another file to the same node in a new revision.
    $this->replaceNodeFile($test_file, $field_name, $nid);
    $node = $node_storage->load($nid);
    $node_file_r2 = File::load($node->{$field_name}->target_id);
    $node_vid_r2 = $node->getRevisionId();
    $this->assertFileExists($node_file_r2->getFileUri());
    $this->assertFileEntryExists($node_file_r2, 'Replacement file entry exists in database after creating new revision.');
    $this->assertFileIsPermanent($node_file_r2, 'Replacement file is permanent.');

    // Check that the original file is still in place on the first revision.
    $node = $node_storage->loadRevision($node_vid_r1);
    $current_file = File::load($node->{$field_name}->target_id);
    $this->assertEquals($node_file_r1->id(), $current_file->id(), 'Original file still in place after replacing file in new revision.');
    $this->assertFileExists($node_file_r1->getFileUri());
    $this->assertFileEntryExists($node_file_r1, 'Original file entry still in place after replacing file in new revision');
    $this->assertFileIsPermanent($node_file_r1, 'Original file is still permanent.');

    // Save a new version of the node without any changes.
    // Check that the file is still the same as the previous revision.
    $this->drupalGet('node/' . $nid . '/edit');
    $this->submitForm(['revision' => '1'], 'Save');
    $node = $node_storage->load($nid);
    $node_file_r3 = File::load($node->{$field_name}->target_id);
    $node_vid_r3 = $node->getRevisionId();
    $this->assertEquals($node_file_r2->id(), $node_file_r3->id(), 'Previous revision file still in place after creating a new revision without a new file.');
    $this->assertFileIsPermanent($node_file_r3, 'New revision file is permanent.');

    // Revert to the first revision and check that the original file is active.
    $this->drupalGet('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert');
    $this->submitForm([], 'Revert');
    $node = $node_storage->load($nid);
    $node_file_r4 = File::load($node->{$field_name}->target_id);
    $this->assertEquals($node_file_r1->id(), $node_file_r4->id(), 'Original revision file still in place after reverting to the original revision.');
    $this->assertFileIsPermanent($node_file_r4, 'Original revision file still permanent after reverting to the original revision.');

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

    // Attach the second file to a user.
    $user = $this->drupalCreateUser();
    $user->$field_name->target_id = $node_file_r3->id();
    $user->$field_name->display = 1;
    $user->save();
    $this->drupalGet('user/' . $user->id() . '/edit');

    // Delete the third revision and check that the file is not deleted yet.
    $this->drupalGet('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete');
    $this->submitForm([], 'Delete');
    $this->assertFileExists($node_file_r3->getFileUri());
    $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting third revision, since it is being used by the user.');
    $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting third revision, since it is being used by the user.');

    // Delete the user and check that the file is also deleted.
    $user->delete();

    // Call file_cron() to clean up the file. Make sure the changed timestamp
    // of the file is older than the system.file.temporary_maximum_age
    // configuration value. We use an UPDATE statement because using the API
    // would set the timestamp.
    $connection = Database::getConnection();
    $connection->update('file_managed')
      ->fields([
        'changed' => \Drupal::time()->getRequestTime() - ($this->config('system.file')->get('temporary_maximum_age') + 1),
      ])
      ->condition('fid', $node_file_r3->id())
      ->execute();
    \Drupal::service('cron')->run();

    $this->assertFileDoesNotExist($node_file_r3->getFileUri());
    $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.');

    // Delete the entire node and check that the original file is deleted.
    $this->drupalGet('node/' . $nid . '/delete');
    $this->submitForm([], 'Delete');
    // Call file_cron() to clean up the file. Make sure the changed timestamp
    // of the file is older than the system.file.temporary_maximum_age
    // configuration value. We use an UPDATE statement because using the API
    // would set the timestamp.
    $connection->update('file_managed')
      ->fields([
        'changed' => \Drupal::time()->getRequestTime() - ($this->config('system.file')->get('temporary_maximum_age') + 1),
      ])
      ->condition('fid', $node_file_r1->id())
      ->execute();
    \Drupal::service('cron')->run();
    $this->assertFileDoesNotExist($node_file_r1->getFileUri());
    $this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.');
  }

}