diff --git a/core/modules/file/config/install/file.settings.yml b/core/modules/file/config/install/file.settings.yml index e652277..16ebe66 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' - +file_usage_temporary: false diff --git a/core/modules/file/config/schema/file.schema.yml b/core/modules/file/config/schema/file.schema.yml index b9f8918..988bea9 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' + file_usage_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 05eedf0..3afb9dc 100644 --- a/core/modules/file/file.install +++ b/core/modules/file/file.install @@ -120,3 +120,24 @@ function file_requirements($phase) { return $requirements; } + +/** + * @addtogroup updates-8.2.0 + * @{ + */ + +/** + * Set default value for marking unused files no longer as temporary. + */ +function file_update_8201() { + // Disable deletion of temporary files. + \Drupal::configFactory()->getEditable('file.settings') + ->set('file_usage_temporary', FALSE) + ->save(); + + return t('Files that have no remaining usages are no longer marked temporary by default.'); +} + +/** + * @} End of "addtogroup updates-8.2.0". + */ diff --git a/core/modules/file/file.module b/core/modules/file/file.module index c63fff4..06be79c 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -1560,3 +1560,29 @@ 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['file_usage_temporary'] = array( + '#type' => 'checkbox', + '#title' => t('Mark unused files as temporary'), + '#default_value' => $config->get('file_usage_temporary'), + '#description' => t('If checked, files for which all usages are removed are marked as temporary and then later on removed. Warning: There are currently known bugs with file usage counting, it is recommended to leave this 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('file_usage_temporary', $form_state->getValue('file_usage_temporary')) + ->save(); +} diff --git a/core/modules/file/file.services.yml b/core/modules/file/file.services.yml index 1c463af..b4b2418 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', '@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 12647b1..44e88da 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; @@ -33,9 +34,11 @@ class DatabaseFileUsageBackend extends FileUsageBase { * @param string $table * (optional) The table to store file usage info. Defaults to 'file_usage'. */ - public function __construct(Connection $connection, $table = 'file_usage') { + public function __construct(Connection $connection, ConfigFactoryInterface $config_factory = NULL, $table = 'file_usage') { + 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..2e02c90 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,23 @@ 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 + * The config factory. + */ + 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 +42,11 @@ 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('file_usage_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/FileFieldRevisionTest.php b/core/modules/file/src/Tests/FileFieldRevisionTest.php index f5d00c7..f3db6f9 100644 --- a/core/modules/file/src/Tests/FileFieldRevisionTest.php +++ b/core/modules/file/src/Tests/FileFieldRevisionTest.php @@ -22,6 +22,9 @@ class FileFieldRevisionTest extends FileFieldTestBase { * should be deleted also. */ function testRevisions() { + + $this->config('file.settings')->set('file_usage_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 708f138..8dbeffc 100644 --- a/core/modules/file/src/Tests/FileListingTest.php +++ b/core/modules/file/src/Tests/FileListingTest.php @@ -30,6 +30,8 @@ class FileListingTest extends FileFieldTestBase { protected function setUp() { parent::setUp(); + $this->config('file.settings')->set('file_usage_temporary', TRUE)->save(); + $this->adminUser = $this->drupalCreateUser(array('access files overview', 'bypass node access')); $this->baseUser = $this->drupalCreateUser(); $this->createFileField('file', 'node', 'article', array(), array('file_extensions' => 'txt png')); diff --git a/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php index 7987bcf..502f757 100644 --- a/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php +++ b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php @@ -29,6 +29,8 @@ class FileOnTranslatedEntityTest extends FileFieldTestBase { protected function setUp() { parent::setUp(); + $this->config('file.settings')->set('file_usage_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 2705ef2..09a34ae 100644 --- a/core/modules/file/src/Tests/FilePrivateTest.php +++ b/core/modules/file/src/Tests/FilePrivateTest.php @@ -26,6 +26,7 @@ protected function setUp() { node_access_test_add_field(NodeType::load('article')); node_access_rebuild(); \Drupal::state()->set('node_access_test.private', TRUE); + $this->config('file.settings')->set('file_usage_temporary', TRUE)->save(); } /** diff --git a/core/modules/file/tests/src/Kernel/DeleteTest.php b/core/modules/file/tests/src/Kernel/DeleteTest.php index b880571..6bed807 100644 --- a/core/modules/file/tests/src/Kernel/DeleteTest.php +++ b/core/modules/file/tests/src/Kernel/DeleteTest.php @@ -28,6 +28,7 @@ function testUnused() { * Tries deleting a file that is in use. */ function testInUse() { + $this->config('file.settings')->set('file_usage_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 8e26015..6cee2cf 100644 --- a/core/modules/file/tests/src/Kernel/UsageTest.php +++ b/core/modules/file/tests/src/Kernel/UsageTest.php @@ -75,10 +75,29 @@ function testAddUsage() { } /** + * Tests file usage deletion when files are made temporary. + */ + function testRemoveUsageTemporary() { + $this->config('file.settings')->set('file_usage_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('file_usage_temporary', FALSE)->save(); + $file = $this->doTestRemoveUsage(); + $this->assertFalse($file->isTemporary()); + } + + /** * Tests \Drupal\file\FileUsage\DatabaseFileUsageBackend::delete(). */ - function testRemoveUsage() { + function doTestRemoveUsage() { $file = $this->createFile(); + $file->setPermanent(); $file_usage = $this->container->get('file.usage'); db_insert('file_usage') ->fields(array( @@ -116,6 +135,7 @@ 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 a8985ce..09ad0f6 100644 --- a/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php +++ b/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php @@ -29,6 +29,8 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase { protected function setUp() { parent::setUp(); + $this->config('file.settings')->set('file_usage_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/user/src/Tests/UserPictureTest.php b/core/modules/user/src/Tests/UserPictureTest.php index 3f8db42..3d2617d 100644 --- a/core/modules/user/src/Tests/UserPictureTest.php +++ b/core/modules/user/src/Tests/UserPictureTest.php @@ -33,6 +33,8 @@ class UserPictureTest extends WebTestBase { protected function setUp() { parent::setUp(); + $this->config('file.settings')->set('file_usage_temporary', TRUE)->save(); + $this->webUser = $this->drupalCreateUser(array( 'access content', 'access comments',