diff --git a/core/includes/file.inc b/core/includes/file.inc index f1c6ac9f357..977d18636ae 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -921,7 +921,7 @@ function file_create_filename($basename, $directory) { * The file id. * * @see file_unmanaged_delete() - * @see file_usage_list() + * @see file_usage()->listUsage() */ function file_delete($fid) { return file_delete_multiple(array($fid)); @@ -938,7 +938,7 @@ function file_delete($fid) { * The file id. * * @see file_unmanaged_delete() - * @see file_usage_list() + * @see file_usage()->listUsage() */ function file_delete_multiple(array $fids) { entity_delete_multiple('file', $fids); @@ -1052,7 +1052,7 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { * Saves a file upload to a new location. * * The file will be added to the {file_managed} table as a temporary file. - * Temporary files are periodically cleaned. Use file_usage_add() to register + * Temporary files are periodically cleaned. Use file_usage()->add() to register * the usage of the file which will automatically mark it as permanent. * * @param $source diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc index 187d57c91d9..28a32a9de6e 100644 --- a/core/modules/file/file.field.inc +++ b/core/modules/file/file.field.inc @@ -219,7 +219,7 @@ function file_field_prepare_view($entity_type, $entities, $field, $instances, $l function file_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { // Add a new usage of each uploaded file. foreach ($items as $item) { - file_usage_add(file_load($item['fid']), 'file', $entity_type, $entity->id()); + file_usage()->add(file_load($item['fid']), 'file', $entity_type, $entity->id()); } } @@ -233,7 +233,7 @@ function file_field_update($entity_type, $entity, $field, $instance, $langcode, // deletion of previous file usages are necessary. if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) { foreach ($items as $item) { - file_usage_add(file_load($item['fid']), 'file', $entity_type, $entity->id()); + file_usage()->add(file_load($item['fid']), 'file', $entity_type, $entity->id()); } return; } @@ -252,7 +252,7 @@ function file_field_update($entity_type, $entity, $field, $instance, $langcode, $original_fids[] = $original_item['fid']; if (isset($original_item['fid']) && !in_array($original_item['fid'], $current_fids)) { // Decrement the file usage count by 1. - file_usage_delete(file_load($original_item['fid']), 'file', $entity_type, $entity->id()); + file_usage()->delete(file_load($original_item['fid']), 'file', $entity_type, $entity->id()); } } } @@ -260,7 +260,7 @@ function file_field_update($entity_type, $entity, $field, $instance, $langcode, // Add new usage entries for newly added files. foreach ($items as $item) { if (!in_array($item['fid'], $original_fids)) { - file_usage_add(file_load($item['fid']), 'file', $entity_type, $entity->id()); + file_usage()->add(file_load($item['fid']), 'file', $entity_type, $entity->id()); } } } @@ -271,7 +271,7 @@ function file_field_update($entity_type, $entity, $field, $instance, $langcode, function file_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { // Delete all file usages within this entity. foreach ($items as $delta => $item) { - file_usage_delete(file_load($item['fid']), 'file', $entity_type, $entity->id(), 0); + file_usage()->delete(file_load($item['fid']), 'file', $entity_type, $entity->id(), 0); } } @@ -281,7 +281,7 @@ function file_field_delete($entity_type, $entity, $field, $instance, $langcode, function file_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $item) { // Decrement the file usage count by 1. - file_usage_delete(file_load($item['fid']), 'file', $entity_type, $entity->id()); + file_usage()->delete(file_load($item['fid']), 'file', $entity_type, $entity->id()); } } diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 62c8fcecfcd..4e6c8eb816f 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -9,6 +9,8 @@ use Drupal\Core\Entity\EntityFieldQuery; use Drupal\file\File; use Drupal\Core\Template\Attribute; use Symfony\Component\HttpFoundation\JsonResponse; +use Drupal\file\FileUsage\DatabaseFileUsageBackend; +use Drupal\file\FileUsage\FileUsageInterface; // Load all Field module hooks for File. require_once DRUPAL_ROOT . '/core/modules/file/file.field.inc'; @@ -146,132 +148,12 @@ function file_load($fid) { } /** - * Determines where a file is used. + * Returns the file usage service. * - * @param Drupal\file\File $file - * A file entity. - * - * @return - * A nested array with usage data. The first level is keyed by module name, - * the second by object type and the third by the object id. The value - * of the third level contains the usage count. - * - * @see file_usage_add() - * @see file_usage_delete() + * @return Drupal\file\FileUsage\FileUsageInterface. */ -function file_usage_list(File $file) { - $result = db_select('file_usage', 'f') - ->fields('f', array('module', 'type', 'id', 'count')) - ->condition('fid', $file->fid) - ->condition('count', 0, '>') - ->execute(); - $references = array(); - foreach ($result as $usage) { - $references[$usage->module][$usage->type][$usage->id] = $usage->count; - } - return $references; -} - -/** - * Records that a module is using a file. - * - * Examples: - * - A module that associates files with nodes, so $type would be - * 'node' and $id would be the node's nid. Files for all revisions are stored - * within a single nid. - * - The User module associates an image with a user, so $type would be 'user' - * and the $id would be the user's uid. - * - * @param Drupal\file\File $file - * A file entity. - * @param $module - * The name of the module using the file. - * @param $type - * The type of the object that contains the referenced file. - * @param $id - * The unique, numeric ID of the object containing the referenced file. - * @param $count - * (optional) The number of references to add to the object. Defaults to 1. - * - * @see file_usage_list() - * @see file_usage_delete() - */ -function file_usage_add(File $file, $module, $type, $id, $count = 1) { - db_merge('file_usage') - ->key(array( - 'fid' => $file->fid, - 'module' => $module, - 'type' => $type, - 'id' => $id, - )) - ->fields(array('count' => $count)) - ->expression('count', 'count + :count', array(':count' => $count)) - ->execute(); - - // Make sure that a used file is permament. - if ($file->status != FILE_STATUS_PERMANENT) { - $file->status = FILE_STATUS_PERMANENT; - $file->save(); - } -} - -/** - * Removes a record to indicate that a module is no longer using a file. - * - * @param Drupal\file\File $file - * A file entity. - * @param $module - * The name of the module using the file. - * @param $type - * (optional) The type of the object that contains the referenced file. May - * be omitted if all module references to a file are being deleted. - * @param $id - * (optional) The unique, numeric ID of the object containing the referenced - * file. May be omitted if all module references to a file are being deleted. - * @param $count - * (optional) The number of references to delete from the object. Defaults to - * 1. 0 may be specified to delete all references to the file within a - * specific object. - * - * @see file_usage_add() - * @see file_usage_list() - */ -function file_usage_delete(File $file, $module, $type = NULL, $id = NULL, $count = 1) { - // Delete rows that have a exact or less value to prevent empty rows. - $query = db_delete('file_usage') - ->condition('module', $module) - ->condition('fid', $file->fid); - if ($type && $id) { - $query - ->condition('type', $type) - ->condition('id', $id); - } - if ($count) { - $query->condition('count', $count, '<='); - } - $result = $query->execute(); - - // If the row has more than the specified count decrement it by that number. - if (!$result && $count > 0) { - $query = db_update('file_usage') - ->condition('module', $module) - ->condition('fid', $file->fid); - if ($type && $id) { - $query - ->condition('type', $type) - ->condition('id', $id); - } - $query->expression('count', 'count - :count', array(':count' => $count)); - $query->execute(); - } - - // If there are no more remaining usages of this file, mark it as temporary, - // which result in a delete through system_cron(). - $usage = file_usage_list($file); - if (empty($usage)) { - $file->status = 0; - $file->save(); - } +function file_usage() { + return drupal_container()->get('file.usage'); } /** @@ -422,7 +304,7 @@ function file_move(File $source, $destination = NULL, $replace = FILE_EXISTS_REN module_invoke_all('file_move', $file, $source); // Delete the original if it's not in use elsewhere. - if ($delete_source && !file_usage_list($source)) { + if ($delete_source && !file_usage()->listUsage($source)) { $source->delete(); } @@ -882,7 +764,7 @@ function file_cron() { )); foreach ($result as $row) { if ($file = file_load($row->fid)) { - $references = file_usage_list($file); + $references = file_usage()->listUsage($file); if (empty($references)) { if (file_exists($file->uri)) { $file->delete(); @@ -1235,7 +1117,7 @@ function file_managed_file_validate(&$element, &$form_state) { if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) { if ($file = file_load($element['fid']['#value'])) { if ($file->status == FILE_STATUS_PERMANENT) { - $references = file_usage_list($file); + $references = file_usage()->listUsage($file); if (empty($references)) { form_error($element, t('The file used in the !name field may not be referenced.', array('!name' => $element['#title']))); } diff --git a/core/modules/file/lib/Drupal/file/FileBundle.php b/core/modules/file/lib/Drupal/file/FileBundle.php new file mode 100644 index 00000000000..79618adf9fd --- /dev/null +++ b/core/modules/file/lib/Drupal/file/FileBundle.php @@ -0,0 +1,19 @@ +register('file.usage', 'Drupal\file\FileUsage\DatabaseFileUsageBackend') + ->addArgument(new Reference('database')); + } +} diff --git a/core/modules/file/lib/Drupal/file/FileUsage/DatabaseFileUsageBackend.php b/core/modules/file/lib/Drupal/file/FileUsage/DatabaseFileUsageBackend.php new file mode 100644 index 00000000000..ff3b0aead9f --- /dev/null +++ b/core/modules/file/lib/Drupal/file/FileUsage/DatabaseFileUsageBackend.php @@ -0,0 +1,116 @@ +connection = $connection; + + $this->tableName = $table; + } + + /** + * Implements Drupal\file\FileUsage\FileUsageInterface::add(). + */ + public function add(File $file, $module, $type, $id, $count = 1) { + $this->connection->merge($this->tableName) + ->key(array( + 'fid' => $file->fid, + 'module' => $module, + 'type' => $type, + 'id' => $id, + )) + ->fields(array('count' => $count)) + ->expression('count', 'count + :count', array(':count' => $count)) + ->execute(); + + parent::add($file, $module, $type, $id, $count); + } + + /** + * Implements Drupal\file\FileUsage\FileUsageInterface::delete(). + */ + public function delete(File $file, $module, $type = NULL, $id = NULL, $count = 1) { + // Delete rows that have a exact or less value to prevent empty rows. + $query = $this->connection->delete($this->tableName) + ->condition('module', $module) + ->condition('fid', $file->fid); + if ($type && $id) { + $query + ->condition('type', $type) + ->condition('id', $id); + } + if ($count) { + $query->condition('count', $count, '<='); + } + $result = $query->execute(); + + // If the row has more than the specified count decrement it by that number. + if (!$result && $count > 0) { + $query = $this->connection->update($this->tableName) + ->condition('module', $module) + ->condition('fid', $file->fid); + if ($type && $id) { + $query + ->condition('type', $type) + ->condition('id', $id); + } + $query->expression('count', 'count - :count', array(':count' => $count)); + $query->execute(); + } + + parent::delete($file, $module, $type, $id, $count); + } + + /** + * Implements Drupal\file\FileUsage\FileUsageInterface::listUsage(). + */ + public function listUsage(File $file) { + $result = $this->connection->select($this->tableName, 'f') + ->fields('f', array('module', 'type', 'id', 'count')) + ->condition('fid', $file->fid) + ->condition('count', 0, '>') + ->execute(); + $references = array(); + foreach ($result as $usage) { + $references[$usage->module][$usage->type][$usage->id] = $usage->count; + } + return $references; + } +} diff --git a/core/modules/file/lib/Drupal/file/FileUsage/FileUsageBase.php b/core/modules/file/lib/Drupal/file/FileUsage/FileUsageBase.php new file mode 100644 index 00000000000..ae2c670310d --- /dev/null +++ b/core/modules/file/lib/Drupal/file/FileUsage/FileUsageBase.php @@ -0,0 +1,40 @@ +status != FILE_STATUS_PERMANENT) { + $file->status = FILE_STATUS_PERMANENT; + $file->save(); + } + } + + /** + * Implements Drupal\file\FileUsage\FileUsageInterface::delete(). + */ + public function delete(File $file, $module, $type = NULL, $id = NULL, $count = 1) { + // If there are no more remaining usages of this file, mark it as temporary, + // which result in a delete through system_cron(). + $usage = file_usage()->listUsage($file); + if (empty($usage)) { + $file->status = 0; + $file->save(); + } + } +} diff --git a/core/modules/file/lib/Drupal/file/FileUsage/FileUsageInterface.php b/core/modules/file/lib/Drupal/file/FileUsage/FileUsageInterface.php new file mode 100644 index 00000000000..4a4b231971e --- /dev/null +++ b/core/modules/file/lib/Drupal/file/FileUsage/FileUsageInterface.php @@ -0,0 +1,75 @@ +createFile(); - file_usage_add($file, 'testing', 'test', 1); - file_usage_add($file, 'testing', 'test', 1); + file_usage()->add($file, 'testing', 'test', 1); + file_usage()->add($file, 'testing', 'test', 1); - file_usage_delete($file, 'testing', 'test', 1); - $usage = file_usage_list($file); + file_usage()->delete($file, 'testing', 'test', 1); + $usage = file_usage()->listUsage($file); $this->assertEqual($usage['testing']['test'], array(1 => 1), t('Test file is still in use.')); $this->assertTrue(file_exists($file->uri), t('File still exists on the disk.')); $this->assertTrue(file_load($file->fid), t('File still exists in the database.')); @@ -50,8 +50,8 @@ class DeleteTest extends FileManagedTestBase { // Clear out the call to hook_file_load(). file_test_reset(); - file_usage_delete($file, 'testing', 'test', 1); - $usage = file_usage_list($file); + file_usage()->delete($file, 'testing', 'test', 1); + $usage = file_usage()->listUsage($file); $this->assertFileHooksCalled(array('load', 'update')); $this->assertTrue(empty($usage), t('File usage data was removed.')); $this->assertTrue(file_exists($file->uri), 'File still exists on the disk.'); diff --git a/core/modules/file/lib/Drupal/file/Tests/UsageTest.php b/core/modules/file/lib/Drupal/file/Tests/UsageTest.php index b3239fd99c6..00c82efdda2 100644 --- a/core/modules/file/lib/Drupal/file/Tests/UsageTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/UsageTest.php @@ -20,7 +20,7 @@ class UsageTest extends FileManagedTestBase { } /** - * Tests file_usage_list(). + * Tests file_usage()->listUsage(). */ function testGetUsage() { $file = $this->createFile(); @@ -43,7 +43,7 @@ class UsageTest extends FileManagedTestBase { )) ->execute(); - $usage = file_usage_list($file); + $usage = file_usage()->listUsage($file); $this->assertEqual(count($usage['testing']), 2, t('Returned the correct number of items.')); $this->assertTrue(isset($usage['testing']['foo'][1]), t('Returned the correct id.')); @@ -53,15 +53,15 @@ class UsageTest extends FileManagedTestBase { } /** - * Tests file_usage_add(). + * Tests file_usage()->add(). */ function testAddUsage() { $file = $this->createFile(); - file_usage_add($file, 'testing', 'foo', 1); + file_usage()->add($file, 'testing', 'foo', 1); // Add the file twice to ensure that the count is incremented rather than // creating additional records. - file_usage_add($file, 'testing', 'bar', 2); - file_usage_add($file, 'testing', 'bar', 2); + file_usage()->add($file, 'testing', 'bar', 2); + file_usage()->add($file, 'testing', 'bar', 2); $usage = db_select('file_usage', 'f') ->fields('f') @@ -78,7 +78,7 @@ class UsageTest extends FileManagedTestBase { } /** - * Tests file_usage_delete(). + * Tests file_usage()->delete(). */ function testRemoveUsage() { $file = $this->createFile(); @@ -93,7 +93,7 @@ class UsageTest extends FileManagedTestBase { ->execute(); // Normal decrement. - file_usage_delete($file, 'testing', 'bar', 2); + file_usage()->delete($file, 'testing', 'bar', 2); $count = db_select('file_usage', 'f') ->fields('f', array('count')) ->condition('f.fid', $file->fid) @@ -102,7 +102,7 @@ class UsageTest extends FileManagedTestBase { $this->assertEqual(2, $count, t('The count was decremented correctly.')); // Multiple decrement and removal. - file_usage_delete($file, 'testing', 'bar', 2, 2); + file_usage()->delete($file, 'testing', 'bar', 2, 2); $count = db_select('file_usage', 'f') ->fields('f', array('count')) ->condition('f.fid', $file->fid) @@ -111,7 +111,7 @@ class UsageTest extends FileManagedTestBase { $this->assertIdentical(FALSE, $count, t('The count was removed entirely when empty.')); // Non-existent decrement. - file_usage_delete($file, 'testing', 'bar', 2); + file_usage()->delete($file, 'testing', 'bar', 2); $count = db_select('file_usage', 'f') ->fields('f', array('count')) ->condition('f.fid', $file->fid) diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 01d2b3b9937..9861931368d 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -373,7 +373,7 @@ function image_field_delete_field($field) { // The value of a managed_file element can be an array if #extended == TRUE. $fid = (is_array($field['settings']['default_image']) ? $field['settings']['default_image']['fid'] : $field['settings']['default_image']); if ($fid && ($file = file_load($fid))) { - file_usage_delete($file, 'image', 'default_image', $field['id']); + file_usage()->delete($file, 'image', 'default_image', $field['id']); } } @@ -397,12 +397,12 @@ function image_field_update_field($field, $prior_field, $has_data) { if ($file_new) { $file_new->status = FILE_STATUS_PERMANENT; $file_new->save(); - file_usage_add($file_new, 'image', 'default_image', $field['id']); + file_usage()->add($file_new, 'image', 'default_image', $field['id']); } // Is there an old file? if ($fid_old && ($file_old = file_load($fid_old))) { - file_usage_delete($file_old, 'image', 'default_image', $field['id']); + file_usage()->delete($file_old, 'image', 'default_image', $field['id']); } } @@ -433,7 +433,7 @@ function image_field_delete_instance($instance) { // Remove the default image when the instance is deleted. if ($fid && ($file = file_load($fid))) { - file_usage_delete($file, 'image', 'default_image', $instance['id']); + file_usage()->delete($file, 'image', 'default_image', $instance['id']); } } @@ -465,11 +465,11 @@ function image_field_update_instance($instance, $prior_instance) { if ($file_new) { $file_new->status = FILE_STATUS_PERMANENT; $file_new->save(); - file_usage_add($file_new, 'image', 'default_image', $instance['id']); + file_usage()->add($file_new, 'image', 'default_image', $instance['id']); } // Delete the old file, if present. if ($fid_old && ($file_old = file_load($fid_old))) { - file_usage_delete($file_old, 'image', 'default_image', $instance['id']); + file_usage()->delete($file_old, 'image', 'default_image', $instance['id']); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Bundle/BundleTest.php b/core/modules/system/lib/Drupal/system/Tests/Bundle/BundleTest.php index c8cd2ef938b..9e662c969e7 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Bundle/BundleTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Bundle/BundleTest.php @@ -33,6 +33,7 @@ class BundleTest extends WebTestBase { * Test that services provided by module bundles get registered to the DIC. */ function testBundleRegistration() { + $this->assertTrue(drupal_container()->getDefinition('file.usage')->getClass() == 'Drupal\\bundle_test\\TestFileUsage', 'Class has been changed'); $this->assertTrue(drupal_container()->has('bundle_test_class'), 'The bundle_test_class service has been registered to the DIC'); // The event subscriber method in the test class calls drupal_set_message with // a message saying it has fired. This will fire on every page request so it diff --git a/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php b/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php index 768a1b023c4..97b79c18afb 100644 --- a/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php +++ b/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php @@ -21,6 +21,9 @@ class BundleTestBundle extends Bundle $container->register('bundle_test_class', 'Drupal\bundle_test\TestClass') ->addTag('kernel.event_subscriber'); + // Override a default bundle used by core to a dummy class. + $container->register('file.usage', 'Drupal\bundle_test\TestFileUsage'); + // @todo Remove when the 'kernel.event_subscriber' tag above is made to // work: http://drupal.org/node/1706064. $container->get('dispatcher')->addSubscriber($container->get('bundle_test_class')); diff --git a/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/TestFileUsage.php b/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/TestFileUsage.php new file mode 100644 index 00000000000..12cc1376828 --- /dev/null +++ b/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/TestFileUsage.php @@ -0,0 +1,31 @@ +picture_delete)) { $entity->picture = 0; - file_usage_delete($entity->original->picture, 'user', 'user', $entity->uid); + file_usage()->delete($entity->original->picture, 'user', 'user', $entity->uid); file_delete($entity->original->picture->fid); } @@ -126,12 +126,12 @@ class UserStorageController extends DatabaseStorageController { // Move the temporary file into the final location. if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) { $entity->picture = $picture; - file_usage_add($picture, 'user', 'user', $entity->uid); + file_usage()->add($picture, 'user', 'user', $entity->uid); } } // Delete the previous picture if it was deleted or replaced. if (!empty($entity->original->picture->fid)) { - file_usage_delete($entity->original->picture, 'user', 'user', $entity->uid); + file_usage()->delete($entity->original->picture, 'user', 'user', $entity->uid); file_delete($entity->original->picture->fid); } }