Commit 9b29a0da authored by Dries's avatar Dries

- Patch #1361226 by Berdir, aspilicious, cosmicdreams, fago: make the file entity a classed object.

parent b3262427
This diff is collapsed.
<?php
/**
* @file
* Definition of Drupal\Core\File\File.
*/
namespace Drupal\Core\File;
use Drupal\entity\Entity;
/**
* Defines the file entity class.
*/
class File extends Entity {
/**
* The file ID.
*
* @var integer
*/
public $fid;
/**
* The file language code.
*
* @var string
*/
public $langcode = LANGUAGE_NOT_SPECIFIED;
/**
* The uid of the user who is associated with the file.
*
* @var integer
*/
public $uid;
/**
* Name of the file with no path components.
*
* This may differ from the basename of the URI if the file is renamed to
* avoid overwriting an existing file.
*
* @var string
*/
public $filename;
/**
* The URI to access the file (either local or remote).
*
* @var string
*/
public $uri;
/**
* The file's MIME type.
*
* @var string
*/
public $filemime;
/**
* The size of the file in bytes.
*
* @var integer
*/
public $filesize;
/**
* A field indicating the status of the file.
*
* Two status are defined in core: temporary (0) and permanent (1).
* Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed
* during a cron run.
*
* @var integer
*/
public $status;
/**
* UNIX timestamp for when the file was last saved.
*
* @var integer
*/
public $timestamp;
/**
* Overrides Drupal\entity\Entity::id().
*/
public function id() {
return $this->fid;
}
}
<?php
/**
* @file
* Definition of Drupal\Core\File\FileStorageController.
*/
namespace Drupal\Core\File;
use Drupal\entity\EntityDatabaseStorageController;
use Drupal\entity\EntityInterface;
/**
* File storage controller for files.
*/
class FileStorageController extends EntityDatabaseStorageController {
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::create().
*/
public function create(array $values) {
// Automatically detect filename if not set.
if (!isset($values['filename']) && isset($values['uri'])) {
$values['filename'] = drupal_basename($values['uri']);
}
// Automatically detect filemime if not set.
if (!isset($values['filemime']) && isset($values['uri'])) {
$values['filemime'] = file_get_mimetype($values['uri']);
}
return parent::create($values);
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::presave().
*/
protected function preSave(EntityInterface $entity) {
$entity->timestamp = REQUEST_TIME;
$entity->filesize = filesize($entity->uri);
if (!isset($entity->langcode)) {
// Default the file's language code to none, because files are language
// neutral more often than language dependent. Until we have better
// flexible settings.
// @todo See http://drupal.org/node/258785 and followups.
$entity->langcode = LANGUAGE_NOT_SPECIFIED;
}
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::preDelete().
*/
public function preDelete($entities) {
foreach ($entities as $entity) {
// Delete the actual file. Failures due to invalid files and files that
// were already deleted are logged to watchdog but ignored, the
// corresponding file entity will be deleted.
file_unmanaged_delete($entity->uri);
}
// Delete corresponding file usage entries.
db_delete('file_usage')
->condition('fid', array_keys($entities), 'IN')
->execute();
}
}
......@@ -137,7 +137,7 @@ class EntityCrudHookTestCase extends WebTestBase {
public function testFileHooks() {
$url = 'public://entity_crud_hook_test.file';
file_put_contents($url, 'Test test test');
$file = (object) array(
$file = entity_create('file', array(
'fid' => NULL,
'uid' => 1,
'filename' => 'entity_crud_hook_test.file',
......@@ -146,9 +146,9 @@ class EntityCrudHookTestCase extends WebTestBase {
'filesize' => filesize($url),
'status' => 1,
'timestamp' => REQUEST_TIME,
);
));
$_SESSION['entity_crud_hook_test'] = array();
file_save($file);
$file->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_file_presave called',
......@@ -167,7 +167,7 @@ class EntityCrudHookTestCase extends WebTestBase {
$_SESSION['entity_crud_hook_test'] = array();
$file->filename = 'new.entity_crud_hook_test.file';
file_save($file);
$file->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_file_presave called',
......@@ -177,7 +177,7 @@ class EntityCrudHookTestCase extends WebTestBase {
));
$_SESSION['entity_crud_hook_test'] = array();
file_delete($file);
$file->delete();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_file_predelete called',
......
......@@ -6,6 +6,7 @@
*/
use Drupal\entity\EntityFieldQuery;
use Drupal\Core\File\File;
// Load all Field module hooks for File.
require_once DRUPAL_ROOT . '/core/modules/file/file.field.inc';
......@@ -347,7 +348,7 @@ function file_progress_implementation() {
/**
* Implements hook_file_predelete().
*/
function file_file_predelete($file) {
function file_file_predelete(File $file) {
// TODO: Remove references to a file that is in-use.
}
......@@ -601,7 +602,7 @@ function file_managed_file_submit($form, &$form_state) {
// it's up to the implementing module to remove usages of files to have them
// removed.
if ($element['#file'] && $element['#file']->status == 0) {
file_delete($element['#file']);
file_delete($element['#file']->fid);
}
// Update both $form_state['values'] and $form_state['input'] to reflect
// that the file has been removed, so that the form is rebuilt correctly.
......@@ -631,7 +632,7 @@ function file_managed_file_submit($form, &$form_state) {
* The FAPI element whose values are being saved.
*
* @return
* The file object representing the file that was saved, or FALSE if no file
* The file entity representing the file that was saved, or FALSE if no file
* was saved.
*/
function file_managed_file_save_upload($element) {
......@@ -735,7 +736,8 @@ function theme_file_link($variables) {
$icon_directory = $variables['icon_directory'];
$url = file_create_url($file->uri);
$icon = theme('file_icon', array('file' => $file, 'icon_directory' => $icon_directory));
// theme_file_icon() requires a file entity, make sure it gets one.
$icon = theme('file_icon', array('file' => ($file instanceof File) ? $file : file_load($file->fid), 'icon_directory' => $icon_directory));
// Set options as per anchor format described at
// http://microformats.org/wiki/file-format-examples
......@@ -762,7 +764,7 @@ function theme_file_link($variables) {
*
* @param $variables
* An associative array containing:
* - file: A file object for which to make an icon.
* - file: A file entity for which to make an icon.
* - icon_directory: (optional) A path to a directory of icons to be used for
* files. Defaults to the value of the "file_icon_directory" variable.
*
......@@ -778,10 +780,10 @@ function theme_file_icon($variables) {
}
/**
* Creates a URL to the icon for a file object.
* Creates a URL to the icon for a file entity.
*
* @param $file
* A file object.
* @param Drupal\Core\File\File $file
* A file entity.
* @param $icon_directory
* (optional) A path to a directory of icons to be used for files. Defaults to
* the value of the "file_icon_directory" variable.
......@@ -789,7 +791,7 @@ function theme_file_icon($variables) {
* @return
* A URL string to the icon, or FALSE if an appropriate icon cannot be found.
*/
function file_icon_url($file, $icon_directory = NULL) {
function file_icon_url(File $file, $icon_directory = NULL) {
if ($icon_path = file_icon_path($file, $icon_directory)) {
return base_path() . $icon_path;
}
......@@ -797,10 +799,10 @@ function file_icon_url($file, $icon_directory = NULL) {
}
/**
* Creates a path to the icon for a file object.
* Creates a path to the icon for a file entity.
*
* @param $file
* A file object.
* @param Drupal\Core\File\File $file
* A file entity.
* @param $icon_directory
* (optional) A path to a directory of icons to be used for files. Defaults to
* the value of the "file_icon_directory" variable.
......@@ -809,7 +811,7 @@ function file_icon_url($file, $icon_directory = NULL) {
* A string to the icon as a local path, or FALSE if an appropriate icon could
* not be found.
*/
function file_icon_path($file, $icon_directory = NULL) {
function file_icon_path(File $file, $icon_directory = NULL) {
// Use the default set of icons if none specified.
if (!isset($icon_directory)) {
$icon_directory = variable_get('file_icon_directory', drupal_get_path('module', 'file') . '/icons');
......@@ -852,13 +854,13 @@ function file_icon_path($file, $icon_directory = NULL) {
/**
* Determines the generic icon MIME package based on a file's MIME type.
*
* @param $file
* A file object.
* @param Drupal\Core\File\File $file
* A file entity.
*
* @return
* The generic icon MIME package expected for this file.
*/
function file_icon_map($file) {
function file_icon_map(File $file) {
switch ($file->filemime) {
// Word document types.
case 'application/msword':
......@@ -984,8 +986,8 @@ function file_icon_map($file) {
/**
* Retrieves a list of references to a file.
*
* @param $file
* A file object.
* @param Drupal\Core\File\File $file
* A file entity.
* @param $field
* (optional) A field array to be used for this check. If given, limits the
* reference check to the given field.
......@@ -1000,7 +1002,7 @@ function file_icon_map($file) {
* @return
* An integer value.
*/
function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') {
function file_get_file_references(File $file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') {
$references = drupal_static(__FUNCTION__, array());
$fields = isset($field) ? array($field['field_name'] => $field) : field_info_fields();
......
......@@ -41,7 +41,7 @@ class FileFieldTestCase extends WebTestBase {
// Add a filesize property to files as would be read by file_load().
$file->filesize = filesize($file->uri);
return $file;
return entity_create('file', (array) $file);
}
/**
......
......@@ -7,6 +7,7 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Drupal\Core\File\File;
/**
* Image style constant for user presets in the database.
......@@ -311,7 +312,7 @@ function image_file_download($uri) {
/**
* Implements hook_file_move().
*/
function image_file_move($file, $source) {
function image_file_move(File $file, File $source) {
// Delete any image derivatives at the original image path.
image_path_flush($source->uri);
}
......@@ -319,7 +320,7 @@ function image_file_move($file, $source) {
/**
* Implements hook_file_predelete().
*/
function image_file_predelete($file) {
function image_file_predelete(File $file) {
// Delete any image derivatives of this image.
image_path_flush($file->uri);
}
......@@ -396,7 +397,7 @@ function image_field_update_field($field, $prior_field, $has_data) {
// Is there a new file?
if ($file_new) {
$file_new->status = FILE_STATUS_PERMANENT;
file_save($file_new);
$file_new->save();
file_usage_add($file_new, 'image', 'default_image', $field['id']);
}
......@@ -464,7 +465,7 @@ function image_field_update_instance($instance, $prior_instance) {
// Save the new file, if present.
if ($file_new) {
$file_new->status = FILE_STATUS_PERMANENT;
file_save($file_new);
$file_new->save();
file_usage_add($file_new, 'image', 'default_image', $instance['id']);
}
// Delete the old file, if present.
......
......@@ -1290,8 +1290,8 @@ class ImageFieldDefaultImagesTestCase extends ImageFieldTestCase {
$files = $this->drupalGetTestFiles('image');
$default_images = array();
foreach (array('field', 'instance', 'instance2', 'field_new', 'instance_new') as $image_target) {
$file = array_pop($files);
$file = file_save($file);
$file = entity_create('file', (array) array_pop($files));
$file->save();
$default_images[$image_target] = $file;
}
......
......@@ -297,7 +297,7 @@ function locale_translate_batch_import($filepath, &$context) {
// The filename is either {langcode}.po or {prefix}.{langcode}.po, so
// we can extract the language code to use for the import from the end.
if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
$file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath);
$file = entity_create('file', array('filename' => drupal_basename($filepath), 'uri' => $filepath));
_locale_import_read_po('db-store', $file, array(), $langcode[2]);
$context['results'][] = $filepath;
}
......
......@@ -6,6 +6,7 @@
*/
use Drupal\Core\Utility\UpdateException;
use Drupal\Core\File\File;
/**
* @addtogroup hooks
......@@ -2342,19 +2343,19 @@ function hook_stream_wrappers_alter(&$wrappers) {
}
/**
* Load additional information into file objects.
* Load additional information into file entities.
*
* file_load_multiple() calls this hook to allow modules to load
* additional information into each file.
*
* @param $files
* An array of file objects, indexed by fid.
* An array of file entities, indexed by fid.
*
* @see file_load_multiple()
* @see file_load()
*/
function hook_file_load($files) {
// Add the upload specific data into the file object.
// Add the upload specific data into the file entity.
$result = db_query('SELECT * FROM {upload} u WHERE u.fid IN (:fids)', array(':fids' => array_keys($files)))->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as $record) {
foreach ($record as $key => $value) {
......@@ -2369,15 +2370,15 @@ function hook_file_load($files) {
* This hook lets modules perform additional validation on files. They're able
* to report a failure by returning one or more error messages.
*
* @param $file
* The file object being validated.
* @param Drupal\Core\File\File $file
* The file entity being validated.
* @return
* An array of error messages. If there are no problems with the file return
* an empty array.
*
* @see file_validate()
*/
function hook_file_validate($file) {
function hook_file_validate(Drupal\Core\File\File $file) {
$errors = array();
if (empty($file->filename)) {
......@@ -2397,12 +2398,10 @@ function hook_file_validate($file) {
* doesn't distinguish between files created as a result of a copy or those
* created by an upload.
*
* @param $file
* The file that has just been created.
*
* @see file_save()
* @param Drupal\Core\File\File $file
* The file entity that is about to be created or updated.
*/
function hook_file_presave($file) {
function hook_file_presave(Drupal\Core\File\File $file) {
// Change the file timestamp to an hour prior.
$file->timestamp -= 3600;
}
......@@ -2414,12 +2413,10 @@ function hook_file_presave($file) {
* doesn't distinguish between files created as a result of a copy or those
* created by an upload.
*
* @param $file
* @param Drupal\Core\File\File $file
* The file that has been added.
*
* @see file_save()
*/
function hook_file_insert($file) {
function hook_file_insert(Drupal\Core\File\File $file) {
// Add a message to the log, if the file is a jpg
$validate = file_validate_extensions($file, 'jpg');
if (empty($validate)) {
......@@ -2430,59 +2427,57 @@ function hook_file_insert($file) {
/**
* Respond to a file being updated.
*
* This hook is called when file_save() is called on an existing file.
* This hook is called when an existing file is saved.
*
* @param $file
* @param Drupal\Core\File\File $file
* The file that has just been updated.
*
* @see file_save()
*/
function hook_file_update($file) {
function hook_file_update(Drupal\Core\File\File $file) {
}
/**
* Respond to a file that has been copied.
*
* @param $file
* The newly copied file object.
* @param $source
* @param Drupal\Core\File\File $file
* The newly copied file entity.
* @param Drupal\Core\File\File $source
* The original file before the copy.
*
* @see file_copy()
*/
function hook_file_copy($file, $source) {
function hook_file_copy(Drupal\Core\File\File $file, Drupal\Core\File\File $source) {
}
/**
* Respond to a file that has been moved.
*
* @param $file
* The updated file object after the move.
* @param $source
* The original file object before the move.
* @param Drupal\Core\File\File $file
* The updated file entity after the move.
* @param Drupal\Core\File\File $source
* The original file entity before the move.
*
* @see file_move()
*/
function hook_file_move($file, $source) {
function hook_file_move(Drupal\Core\File\File $file, Drupal\Core\File\File $source) {
}
/**
* Act prior to file deletion.
*
* This hook is invoked from file_delete() before the file is removed from the
* This hook is invoked when deleting a file before the file is removed from the
* filesystem and before its records are removed from the database.
*
* @param $file
* @param Drupal\Core\File\File $file
* The file that is about to be deleted.
*
* @see hook_file_delete()
* @see file_delete()
* @see Drupal\Core\File\FileStorageController::delete()
* @see upload_file_delete()
*/
function hook_file_predelete($file) {
function hook_file_predelete(Drupal\Core\File\File $file) {
// Delete all information associated with the file.
db_delete('upload')->condition('fid', $file->fid)->execute();
}
......@@ -2490,16 +2485,16 @@ function hook_file_predelete($file) {
/**
* Respond to file deletion.
*
* This hook is invoked from file_delete() after the file has been removed from
* This hook is invoked after the file has been removed from
* the filesystem and after its records have been removed from the database.
*
* @param $file
* @param Drupal\Core\File\File $file
* The file that has just been deleted.
*
* @see hook_file_predelete()
* @see file_delete()
* @see Drupal\Core\File\FileStorageController::delete()
*/
function hook_file_delete($file) {
function hook_file_delete(Drupal\Core\File\File $file) {
// Delete all information associated with the file.
db_delete('upload')->condition('fid', $file->fid)->execute();
}
......
......@@ -271,6 +271,8 @@ function system_entity_info() {
'file' => array(
'label' => t('File'),
'base table' => 'file_managed',
'controller class' => 'Drupal\Core\File\FileStorageController',
'entity class' => 'Drupal\Core\File\File',
'entity keys' => array(
'id' => 'fid',
'label' => 'filename',
......@@ -3095,7 +3097,7 @@ function system_cron() {
$references = file_usage_list($file);
if (empty($references)) {
if (file_exists($file->uri)) {
file_delete($file);
$file->delete();
}
else {
watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->uri), WATCHDOG_ERROR);
......
......@@ -870,8 +870,7 @@ class CronRunTestCase extends WebTestBase {
* Ensure that temporary files are removed.
*
* Create files for all the possible combinations of age and status. We are
* using UPDATE statements rather than file_save() because it would set the
* timestamp.
* using UPDATE statements because using the API would set the timestamp.
*/
function testTempFileCleanup() {
// Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
......
......@@ -7,11 +7,12 @@
*/
use Drupal\simpletest\WebTestBase;
use Drupal\Core\File\File;
/**
* Helper validator that returns the $errors parameter.
*/
function file_test_validator($file, $errors) {
function file_test_validator(File $file, $errors) {
return $errors;
}
......@@ -238,11 +239,11 @@ class FileTestCase extends WebTestBase {
$file->timestamp = REQUEST_TIME;
$file->filesize = filesize($file->uri);
$file->status = 0;
// Write the record directly rather than calling file_save() so we don't
// invoke the hooks.
// Write the record directly rather than using the API so we don't invoke
// the hooks.
$this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file');
return $file;
return entity_create('file', (array) $file);
}
}
......@@ -388,11 +389,11 @@ class FileValidatorTest extends WebTestBase {
function setUp() {
parent::setUp();
$this->image = new stdClass();
$this->image = entity_create('file', array());
$this->image->uri = 'core/misc/druplicon.png';
$this->image->filename = drupal_basename($this->image->uri);
$this->non_image = new stdClass();
$this->non_image = entity_create('file', array());