diff --git a/core/modules/file/config/install/file.settings.yml b/core/modules/file/config/install/file.settings.yml
index e652277..9191a0b 100644
--- a/core/modules/file/config/install/file.settings.yml
+++ b/core/modules/file/config/install/file.settings.yml
@@ -3,4 +3,4 @@ description:
length: 128
icon:
directory: 'core/modules/file/icons'
-
+make_unused_managed_files_temporary: false
diff --git a/core/modules/file/config/schema/file.schema.yml b/core/modules/file/config/schema/file.schema.yml
index b9f8918..42d57d9 100644
--- a/core/modules/file/config/schema/file.schema.yml
+++ b/core/modules/file/config/schema/file.schema.yml
@@ -21,6 +21,9 @@ file.settings:
directory:
type: path
label: 'Directory'
+ make_unused_managed_files_temporary:
+ type: boolean
+ label: 'Controls if unused files should be marked temporary'
field.storage_settings.file:
type: base_entity_reference_field_settings
diff --git a/core/modules/file/file.install b/core/modules/file/file.install
index 9134a25..0acdeb0 100644
--- a/core/modules/file/file.install
+++ b/core/modules/file/file.install
@@ -5,6 +5,8 @@
* Install, update and uninstall functions for File module.
*/
+use Drupal\Core\Url;
+
/**
* Implements hook_schema().
*/
@@ -112,7 +114,32 @@ function file_requirements($phase) {
'value' => $value,
'description' => $description,
];
+
+ $file_config = \Drupal::configFactory()->get('file.settings');
+ $requirements['file_orphaned_file_delete'] = [
+ 'title' => t('Orphaned file delete'),
+ 'value' => t('There are currently known bugs with file usage counting. It is recommended to leave \'Schedule all unused files for deletion\' disabled to prevent the loss of files.', [
+ ':url' => Url::fromRoute('system.file_system_settings', [], ['fragment' => 'edit-make-unused-managed-files-temporary'])
+ ->toString()
+ ]),
+ 'severity' => $file_config->get('make_unused_managed_files_temporary') ? REQUIREMENT_WARNING : REQUIREMENT_OK,
+ ];
}
return $requirements;
}
+
+/**
+ * Prevent unused files from being deleted.
+ */
+function file_update_8300() {
+ // Disable deletion of unused permanent files.
+ \Drupal::configFactory()->getEditable('file.settings')
+ ->set('make_unused_managed_files_temporary', FALSE)
+ ->save();
+
+ return t('Files that have no remaining usages are no longer deleted by default. It is recommended to leave \'Schedule all unused files for deletion\' disabled to prevent the loss of files.', [
+ ':url' => Url::fromRoute('system.file_system_settings', [], ['fragment' => 'edit-make-unused-managed-files-temporary'])
+ ->toString()
+ ]);
+}
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 6902db6..e00b2d0 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1568,3 +1568,41 @@ function _views_file_status($choice = NULL) {
return $status;
}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function file_form_system_file_system_settings_alter(&$form, FormStateInterface $form_state) {
+ $config = \Drupal::configFactory()->getEditable('file.settings');
+
+ $form['temporary_files'] = [
+ '#type' => 'details',
+ '#title' => t('Temporary and other unused files'),
+ '#open' => TRUE
+ ];
+
+ // Move the temporary_maximum_age settings.
+ $temporary_maximum_age_element = $form['temporary_maximum_age'];
+ unset($form['temporary_maximum_age']);
+ $form['temporary_files']['temporary_maximum_age'] = $temporary_maximum_age_element;
+
+ $form['temporary_files']['make_unused_managed_files_temporary'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Schedule all unused files for deletion'),
+ '#default_value' => $config->get('make_unused_managed_files_temporary'),
+ '#description' => t('If enabled, all files that are not referenced will be deleted after the time set above. For example, enabling this will delete files that previously were used by deleted content. Warning: There are currently known bugs with file usage counting. It is recommended to leave disabled to prevent the loss of files.'),
+ ];
+
+ $form['#submit'][] = 'file_system_file_settings_submit';
+}
+
+/**
+ * Form submission handler for system_logging_settings().
+ *
+ * @see syslog_form_system_logging_settings_alter()
+ */
+function file_system_file_settings_submit($form, FormStateInterface $form_state) {
+ \Drupal::configFactory()->getEditable('file.settings')
+ ->set('make_unused_managed_files_temporary', $form_state->getValue('make_unused_managed_files_temporary'))
+ ->save();
+}
diff --git a/core/modules/file/file.services.yml b/core/modules/file/file.services.yml
index 1c463af..03eb7bc 100644
--- a/core/modules/file/file.services.yml
+++ b/core/modules/file/file.services.yml
@@ -1,6 +1,6 @@
services:
file.usage:
class: Drupal\file\FileUsage\DatabaseFileUsageBackend
- arguments: ['@database']
+ arguments: ['@database', 'file_usage', '@config.factory']
tags:
- { name: backend_overridable }
diff --git a/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php b/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php
index cfb0c44..2b19954 100644
--- a/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php
+++ b/core/modules/file/src/FileUsage/DatabaseFileUsageBackend.php
@@ -2,6 +2,7 @@
namespace Drupal\file\FileUsage;
+use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\file\FileInterface;
@@ -32,8 +33,11 @@ class DatabaseFileUsageBackend extends FileUsageBase {
* information.
* @param string $table
* (optional) The table to store file usage info. Defaults to 'file_usage'.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * (optional) The config factory.
*/
- public function __construct(Connection $connection, $table = 'file_usage') {
+ public function __construct(Connection $connection, $table = 'file_usage', ConfigFactoryInterface $config_factory = NULL) {
+ parent::__construct($config_factory);
$this->connection = $connection;
$this->tableName = $table;
diff --git a/core/modules/file/src/FileUsage/FileUsageBase.php b/core/modules/file/src/FileUsage/FileUsageBase.php
index c90359b..ba59cd5 100644
--- a/core/modules/file/src/FileUsage/FileUsageBase.php
+++ b/core/modules/file/src/FileUsage/FileUsageBase.php
@@ -2,6 +2,7 @@
namespace Drupal\file\FileUsage;
+use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\file\FileInterface;
/**
@@ -10,6 +11,27 @@
abstract class FileUsageBase implements FileUsageInterface {
/**
+ * The config factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * Creates a FileUsageBase object.
+ *
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * (optional) The config factory. Defaults to NULL and will use
+ * \Drupal::configFactory() instead.
+ *
+ * @deprecated The $config_factory parameter will become required in Drupal
+ * 9.0.0.
+ */
+ public function __construct(ConfigFactoryInterface $config_factory = NULL) {
+ $this->configFactory = $config_factory ?: \Drupal::configFactory();
+ }
+
+ /**
* {@inheritdoc}
*/
public function add(FileInterface $file, $module, $type, $id, $count = 1) {
@@ -24,6 +46,10 @@ public function add(FileInterface $file, $module, $type, $id, $count = 1) {
* {@inheritdoc}
*/
public function delete(FileInterface $file, $module, $type = NULL, $id = NULL, $count = 1) {
+ // Do not actually mark files as temporary when the behavior is disabled.
+ if (!$this->configFactory->get('file.settings')->get('make_unused_managed_files_temporary')) {
+ return;
+ }
// If there are no more remaining usages of this file, mark it as temporary,
// which result in a delete through system_cron().
$usage = \Drupal::service('file.usage')->listUsage($file);
diff --git a/core/modules/file/src/Tests/FileAdminTest.php b/core/modules/file/src/Tests/FileAdminTest.php
new file mode 100644
index 0000000..f352b29
--- /dev/null
+++ b/core/modules/file/src/Tests/FileAdminTest.php
@@ -0,0 +1,33 @@
+drupalLogin($this->drupalCreateUser(['administer site configuration']));
+ $this->assertFalse($this->config('file.settings')
+ ->get('make_unused_managed_files_temporary'), 'The file.settings:make_unused_managed_files_temporary is set to FALSE.');
+ $this->drupalPostForm('admin/config/media/file-system', ['make_unused_managed_files_temporary' => TRUE], t('Save configuration'));
+ $this->assertTrue($this->config('file.settings')
+ ->get('make_unused_managed_files_temporary'), 'The file.settings:make_unused_managed_files_temporary has been set to TRUE.');
+ }
+
+}
diff --git a/core/modules/file/src/Tests/FileFieldRevisionTest.php b/core/modules/file/src/Tests/FileFieldRevisionTest.php
index 7b7b4d3..04fbcb8 100644
--- a/core/modules/file/src/Tests/FileFieldRevisionTest.php
+++ b/core/modules/file/src/Tests/FileFieldRevisionTest.php
@@ -22,6 +22,11 @@ class FileFieldRevisionTest extends FileFieldTestBase {
* should be deleted also.
*/
public function testRevisions() {
+ // 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.manager')->getStorage('node');
$type_name = 'article';
$field_name = strtolower($this->randomMachineName());
diff --git a/core/modules/file/src/Tests/FileListingTest.php b/core/modules/file/src/Tests/FileListingTest.php
index e348949..cca21c5 100644
--- a/core/modules/file/src/Tests/FileListingTest.php
+++ b/core/modules/file/src/Tests/FileListingTest.php
@@ -30,6 +30,11 @@ class FileListingTest extends FileFieldTestBase {
protected function setUp() {
parent::setUp();
+ // This test expects unused managed files to be marked as a temporary file.
+ $this->config('file.settings')
+ ->set('make_unused_managed_files_temporary', TRUE)
+ ->save();
+
$this->adminUser = $this->drupalCreateUser(['access files overview', 'bypass node access']);
$this->baseUser = $this->drupalCreateUser();
$this->createFileField('file', 'node', 'article', [], ['file_extensions' => 'txt png']);
diff --git a/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
index 5f1e707..b8a22ae 100644
--- a/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
+++ b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
@@ -29,6 +29,11 @@ class FileOnTranslatedEntityTest extends FileFieldTestBase {
protected function setUp() {
parent::setUp();
+ // This test expects unused managed files to be marked as temporary a file.
+ $this->config('file.settings')
+ ->set('make_unused_managed_files_temporary', TRUE)
+ ->save();
+
// Create the "Basic page" node type.
// @todo Remove the disabling of new revision creation in
// https://www.drupal.org/node/1239558.
diff --git a/core/modules/file/src/Tests/FilePrivateTest.php b/core/modules/file/src/Tests/FilePrivateTest.php
index 6808a47..0174a89 100644
--- a/core/modules/file/src/Tests/FilePrivateTest.php
+++ b/core/modules/file/src/Tests/FilePrivateTest.php
@@ -27,6 +27,10 @@ protected function setUp() {
node_access_test_add_field(NodeType::load('article'));
node_access_rebuild();
\Drupal::state()->set('node_access_test.private', TRUE);
+ // This test expects unused managed files to be marked as a temporary file.
+ $this->config('file.settings')
+ ->set('make_unused_managed_files_temporary', TRUE)
+ ->save();
}
/**
diff --git a/core/modules/file/tests/src/Kernel/DeleteTest.php b/core/modules/file/tests/src/Kernel/DeleteTest.php
index 3b868c0..de5a5bc 100644
--- a/core/modules/file/tests/src/Kernel/DeleteTest.php
+++ b/core/modules/file/tests/src/Kernel/DeleteTest.php
@@ -28,6 +28,11 @@ public function testUnused() {
* Tries deleting a file that is in use.
*/
public function testInUse() {
+ // 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();
$file = $this->createFile();
$file_usage = $this->container->get('file.usage');
$file_usage->add($file, 'testing', 'test', 1);
diff --git a/core/modules/file/tests/src/Kernel/UsageTest.php b/core/modules/file/tests/src/Kernel/UsageTest.php
index 672de0f..88812a1 100644
--- a/core/modules/file/tests/src/Kernel/UsageTest.php
+++ b/core/modules/file/tests/src/Kernel/UsageTest.php
@@ -75,10 +75,33 @@ public function testAddUsage() {
}
/**
+ * Tests file usage deletion when files are made temporary.
+ */
+ function testRemoveUsageTemporary() {
+ $this->config('file.settings')
+ ->set('make_unused_managed_files_temporary', TRUE)
+ ->save();
+ $file = $this->doTestRemoveUsage();
+ $this->assertTrue($file->isTemporary());
+ }
+
+ /**
+ * Tests file usage deletion when files are made temporary.
+ */
+ function testRemoveUsageNonTemporary() {
+ $this->config('file.settings')
+ ->set('make_unused_managed_files_temporary', FALSE)
+ ->save();
+ $file = $this->doTestRemoveUsage();
+ $this->assertFalse($file->isTemporary());
+ }
+
+ /**
* Tests \Drupal\file\FileUsage\DatabaseFileUsageBackend::delete().
*/
- public function testRemoveUsage() {
+ public function doTestRemoveUsage() {
$file = $this->createFile();
+ $file->setPermanent();
$file_usage = $this->container->get('file.usage');
db_insert('file_usage')
->fields([
@@ -116,6 +139,7 @@ public function testRemoveUsage() {
->execute()
->fetchField();
$this->assertIdentical(FALSE, $count, 'Decrementing non-exist record complete.');
+ return $file;
}
/**
diff --git a/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php b/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
index e1f70d7..c5f16cf 100644
--- a/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
+++ b/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
@@ -29,6 +29,9 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
protected function setUp() {
parent::setUp();
+ // This test expects unused managed files to be marked as a temporary file.
+ $this->config('file.settings')->set('make_unused_managed_files_temporary', TRUE)->save();
+
// Create the "Basic page" node type.
// @todo Remove the disabling of new revision creation in
// https://www.drupal.org/node/1239558.
diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php
index ff0b792..d3a6cae 100644
--- a/core/modules/system/src/Form/FileSystemForm.php
+++ b/core/modules/system/src/Form/FileSystemForm.php
@@ -125,10 +125,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$period[0] = t('Never');
$form['temporary_maximum_age'] = [
'#type' => 'select',
- '#title' => t('Delete orphaned files after'),
+ '#title' => t('Delete temporary files after'),
'#default_value' => $config->get('temporary_maximum_age'),
'#options' => $period,
- '#description' => t('Orphaned files are not referenced from any content but remain in the file system and may appear in administrative listings. Warning: If enabled, orphaned files will be permanently deleted and may not be recoverable.'),
+ '#description' => t('Temporary files are not referenced, but are in the file system and therefore may show up in administrative lists. Warning: If enabled, temporary files will be permanently deleted and may not be recoverable.'),
];
return parent::buildForm($form, $form_state);
diff --git a/core/modules/user/src/Tests/UserPictureTest.php b/core/modules/user/src/Tests/UserPictureTest.php
index 6d546f0..d850413 100644
--- a/core/modules/user/src/Tests/UserPictureTest.php
+++ b/core/modules/user/src/Tests/UserPictureTest.php
@@ -33,6 +33,12 @@ class UserPictureTest extends WebTestBase {
protected function setUp() {
parent::setUp();
+ // This test expects unused managed files to be marked temporary and then
+ // cleaned up by file_cron().
+ $this->config('file.settings')
+ ->set('make_unused_managed_files_temporary', TRUE)
+ ->save();
+
$this->webUser = $this->drupalCreateUser([
'access content',
'access comments',