Commit 6d54ed7a authored by alexpott's avatar alexpott

Issue #1818568 by Berdir, das-peter: Convert files to the new Entity Field API.

parent db0104f4
......@@ -1156,22 +1156,22 @@ function file_save_upload($form_field_name, $validators = array(), $destination
if (!empty($extensions)) {
// Munge the filename to protect against possible malicious extension
// hiding within an unknown file type (ie: filename.html.foo).
$file->filename = file_munge_filename($file->filename, $extensions);
$file->setFilename(file_munge_filename($file->getFilename(), $extensions));
}
// Rename potentially executable files, to help prevent exploits (i.e. will
// rename filename.php.foo and filename.php to filename.php.foo.txt and
// filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
// evaluates to TRUE.
if (!config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
$file->filemime = 'text/plain';
$file->uri .= '.txt';
$file->filename .= '.txt';
if (!config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
$file->setMimeType('text/plain');
$file->setFileUri($file->getFileUri() . '.txt');
$file->setFilename($file->getFilename() . '.txt');
// The .txt extension may not be in the allowed list of extensions. We have
// to add it here or else the file upload will fail.
if (!empty($extensions)) {
$validators['file_validate_extensions'][0] .= ' txt';
drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename)));
drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->getFilename())));
}
}
......@@ -1193,7 +1193,7 @@ function file_save_upload($form_field_name, $validators = array(), $destination
if (substr($destination, -1) != '/') {
$destination .= '/';
}
$file->destination = file_destination($destination . $file->filename, $replace);
$file->destination = file_destination($destination . $file->getFilename(), $replace);
// If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR and
// there's an existing file so we need to bail.
if ($file->destination === FALSE) {
......@@ -1210,7 +1210,7 @@ function file_save_upload($form_field_name, $validators = array(), $destination
// Check for errors.
if (!empty($errors)) {
$message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
$message = t('The specified file %name could not be uploaded.', array('%name' => $file->getFilename()));
if (count($errors) > 1) {
$message .= theme('item_list', array('items' => $errors));
}
......@@ -1226,7 +1226,7 @@ function file_save_upload($form_field_name, $validators = array(), $destination
// directory. This overcomes open_basedir restrictions for future file
// operations.
$file->uri = $file->destination;
if (!drupal_move_uploaded_file($uploaded_files['files']['tmp_name'][$form_field_name][$i], $file->uri)) {
if (!drupal_move_uploaded_file($uploaded_files['files']['tmp_name'][$form_field_name][$i], $file->getFileUri())) {
form_set_error($form_field_name, t('File upload error. Could not move uploaded file.'));
watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri));
$files[$i] = FALSE;
......@@ -1234,14 +1234,14 @@ function file_save_upload($form_field_name, $validators = array(), $destination
}
// Set the permissions on the new file.
drupal_chmod($file->uri);
drupal_chmod($file->getFileUri());
// If we are replacing an existing file re-use its database record.
if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->uri));
$existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->getFileUri()));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->fid;
$file->fid = $existing->id();
}
}
......
......@@ -157,7 +157,7 @@ public function submitForm(array &$form, array &$form_state) {
$data = '';
$validators = array('file_validate_extensions' => array('opml xml'));
if ($file = file_save_upload('upload', $validators, FALSE, 0)) {
$data = file_get_contents($file->uri);
$data = file_get_contents($file->getFileUri());
}
else {
// @todo Move this to a fetcher implementation.
......
......@@ -49,7 +49,7 @@ 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 Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The file entity being validated.
* @return
* An array of error messages. If there are no problems with the file return
......@@ -57,13 +57,13 @@ function hook_file_load($files) {
*
* @see file_validate()
*/
function hook_file_validate(Drupal\file\File $file) {
function hook_file_validate(Drupal\file\FileInterface $file) {
$errors = array();
if (empty($file->filename)) {
if (!$file->getFilename()) {
$errors[] = t("The file's name is empty. Please give a name to the file.");
}
if (strlen($file->filename) > 255) {
if (strlen($file->getFilename()) > 255) {
$errors[] = t("The file's name exceeds the 255 characters limit. Please rename the file and try again.");
}
......@@ -77,10 +77,10 @@ function hook_file_validate(Drupal\file\File $file) {
* doesn't distinguish between files created as a result of a copy or those
* created by an upload.
*
* @param Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The file entity that is about to be created or updated.
*/
function hook_file_presave(Drupal\file\File $file) {
function hook_file_presave(Drupal\file\FileInterface $file) {
// Change the file timestamp to an hour prior.
$file->timestamp -= 3600;
}
......@@ -92,10 +92,10 @@ function hook_file_presave(Drupal\file\File $file) {
* doesn't distinguish between files created as a result of a copy or those
* created by an upload.
*
* @param Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The file that has been added.
*/
function hook_file_insert(Drupal\file\File $file) {
function hook_file_insert(Drupal\file\FileInterface $file) {
// Add a message to the log, if the file is a jpg
$validate = file_validate_extensions($file, 'jpg');
if (empty($validate)) {
......@@ -108,60 +108,57 @@ function hook_file_insert(Drupal\file\File $file) {
*
* This hook is called when an existing file is saved.
*
* @param Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The file that has just been updated.
*/
function hook_file_update(Drupal\file\File $file) {
$file_user = user_load($file->uid);
function hook_file_update(Drupal\file\FileInterface $file) {
// Make sure that the file name starts with the owner's user name.
if (strpos($file->filename, $file_user->name) !== 0) {
$old_filename = $file->filename;
$file->filename = $file_user->name . '_' . $file->filename;
if (strpos($file->getFilename(), $file->getOwner()->name) !== 0) {
$old_filename = $file->getFilename();
$file->setFilename($file->getOwner()->name . '_' . $file->getFilename());
$file->save();
watchdog('file', t('%source has been renamed to %destination', array('%source' => $old_filename, '%destination' => $file->filename)));
watchdog('file', t('%source has been renamed to %destination', array('%source' => $old_filename, '%destination' => $file->getFilename())));
}
}
/**
* Respond to a file that has been copied.
*
* @param Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The newly copied file entity.
* @param Drupal\file\File $source
* @param \Drupal\file\FileInterface $source
* The original file before the copy.
*
* @see file_copy()
*/
function hook_file_copy(Drupal\file\File $file, Drupal\file\File $source) {
$file_user = user_load($file->uid);
function hook_file_copy(Drupal\file\FileInterface $file, Drupal\file\FileInterface $source) {
// Make sure that the file name starts with the owner's user name.
if (strpos($file->filename, $file_user->name) !== 0) {
$file->filename = $file_user->name . '_' . $file->filename;
if (strpos($file->getFilename(), $file->getOwner()->name) !== 0) {
$file->setFilename($file->getOwner()->name . '_' . $file->getFilename());
$file->save();
watchdog('file', t('Copied file %source has been renamed to %destination', array('%source' => $source->filename, '%destination' => $file->filename)));
watchdog('file', t('Copied file %source has been renamed to %destination', array('%source' => $source->filename, '%destination' => $file->getFilename())));
}
}
/**
* Respond to a file that has been moved.
*
* @param Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The updated file entity after the move.
* @param Drupal\file\File $source
* @param \Drupal\file\FileInterface $source
* The original file entity before the move.
*
* @see file_move()
*/
function hook_file_move(Drupal\file\File $file, Drupal\file\File $source) {
$file_user = user_load($file->uid);
function hook_file_move(Drupal\file\FileInterface $file, Drupal\file\FileInterface $source) {
// Make sure that the file name starts with the owner's user name.
if (strpos($file->filename, $file_user->name) !== 0) {
$file->filename = $file_user->name . '_' . $file->filename;
if (strpos($file->getFilename(), $file->getOwner()->name) !== 0) {
$file->setFilename($file->getOwner()->name . '_' . $file->getFilename());
$file->save();
watchdog('file', t('Moved file %source has been renamed to %destination', array('%source' => $source->filename, '%destination' => $file->filename)));
watchdog('file', t('Moved file %source has been renamed to %destination', array('%source' => $source->filename, '%destination' => $file->getFilename())));
}
}
......@@ -171,16 +168,16 @@ function hook_file_move(Drupal\file\File $file, Drupal\file\File $source) {
* 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 Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The file that is about to be deleted.
*
* @see hook_file_delete()
* @see Drupal\file\FileStorageController::delete()
* @see upload_file_delete()
*/
function hook_file_predelete(Drupal\file\File $file) {
function hook_file_predelete(Drupal\file\FileInterface $file) {
// Delete all information associated with the file.
db_delete('upload')->condition('fid', $file->fid)->execute();
db_delete('upload')->condition('fid', $file->id())->execute();
}
/**
......@@ -189,15 +186,15 @@ function hook_file_predelete(Drupal\file\File $file) {
* This hook is invoked after the file has been removed from
* the filesystem and after its records have been removed from the database.
*
* @param Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The file that has just been deleted.
*
* @see hook_file_predelete()
* @see Drupal\file\FileStorageController::delete()
*/
function hook_file_delete(Drupal\file\File $file) {
function hook_file_delete(Drupal\file\FileInterface $file) {
// Delete all information associated with the file.
db_delete('upload')->condition('fid', $file->fid)->execute();
db_delete('upload')->condition('fid', $file->id())->execute();
}
/**
......@@ -209,9 +206,9 @@ function hook_file_delete(Drupal\file\File $file) {
*
* @param $field
* The field to which the file belongs.
* @param Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity which references the file.
* @param Drupal\file\File $file
* @param \Drupal\file\FileInterface $file
* The file entity that is being requested.
*
* @return
......@@ -221,7 +218,7 @@ function hook_file_delete(Drupal\file\File $file) {
*
* @see hook_field_access().
*/
function hook_file_download_access($field, Drupal\Core\Entity\EntityInterface $entity, Drupal\file\File $file) {
function hook_file_download_access($field, Drupal\Core\Entity\EntityInterface $entity, Drupal\file\FileInterface $file) {
if ($entity->entityType() == 'node') {
return node_access('view', $entity);
}
......
......@@ -211,7 +211,7 @@ function file_field_prepare_view($entity_type, $entities, $field, $instances, $l
$items[$id][$delta] = NULL;
}
else {
$items[$id][$delta] = array_merge($item, (array) $files[$item['fid']]);
$items[$id][$delta]['entity'] = $files[$item['fid']];
}
}
}
......@@ -419,7 +419,7 @@ function file_field_widget_multiple_count_validate($element, &$form_state, $form
$removed_names = array();
foreach ($removed_files as $fid) {
$file = file_load($fid);
$removed_names[] = $file->filename;
$removed_names[] = $file->getFilename();
}
drupal_set_message(
t(
......@@ -654,7 +654,7 @@ function theme_file_widget($variables) {
if (!empty($element['fids']['#value'])) {
// Add the file size after the file name.
$file = reset($element['#files']);
$element['file_' . $file->fid]['filename']['#markup'] .= ' <span class="file-size">(' . format_size($file->filesize) . ')</span> ';
$element['file_' . $file->id()]['filename']['#markup'] .= ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
}
$output .= drupal_render_children($element);
$output .= '</div>';
......@@ -861,8 +861,8 @@ function theme_file_formatter_table($variables) {
$rows = array();
foreach ($variables['items'] as $delta => $item) {
$rows[] = array(
theme('file_link', array('file' => (object) $item)),
format_size($item['filesize']),
theme('file_link', array('file' => $item['entity'])),
format_size($item['entity']->getSize()),
);
}
......
This diff is collapsed.
......@@ -8,10 +8,130 @@
namespace Drupal\file;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\user\UserInterface;
/**
* Provides an interface defining a file entity.
* Defines getter and setter methods for file entity base fields.
*/
interface FileInterface extends ContentEntityInterface {
/**
* Returns the name of the file.
*
* This may differ from the basename of the URI if the file is renamed to
* avoid overwriting an existing file.
*
* @return string
* Name of the file.
*/
public function getFilename();
/**
* Sets the name of the file.
*
* @param string $filename
* The file name that corresponds to this file. May differ from the basename
* of the URI and changing the filename does not change the URI.
*/
public function setFilename($filename);
/**
* Returns the URI of the file.
*
* @return string
* The URI of the file, e.g. public://directory/file.jpg.
*/
public function getFileUri();
/**
* Sets the URI of the file.
*
* @param string $uri
* The URI of the file, e.g. public://directory/file.jpg. Does not change
* the location of the file.
*/
public function setFileUri($uri);
/**
* Returns the MIME type of the file.
*
* @return string
* The MIME type of the file, e.g. image/jpeg or text/xml.
*/
public function getMimeType();
/**
* Sets the MIME type of the file.
*
* @param string $mime
* The MIME type of the file, e.g. image/jpeg or text/xml.
*/
public function setMimeType($mime);
/**
* Returns the size of the file.
*
* @return string
* The size of the file in bytes.
*/
public function getSize();
/**
* Sets the size of the file.
*
* @param int $size
* The size of the file in bytes.
*/
public function setSize($size);
/**
* Returns TRUE if the file is permanent.
*
* @return bool
* TRUE if the file status is permanent.
*/
public function isPermanent();
/**
* Returns TRUE if the file is temporary.
*
* @return bool
* TRUE if the file status is temporary.
*/
public function isTemporary();
/**
* Sets the file status to permanent.
*/
public function setPermanent();
/**
* Sets the file status to temporary.
*/
public function setTemporary();
/**
* Returns the timestamp when the file was created.
*
* @return int
* Creation timestamp of the file.
*/
public function getChangedTime();
/**
* Returns the user that owns this file.
*
* @return \Drupal\user\UserInterface
* The user that owns the file.
*/
public function getOwner();
/**
* Sets the user that owns this file.
*
* @param \Drupal\user\UserInterface $user
* The user that owns the file.
*/
public function setOwner(UserInterface $user);
}
......@@ -7,14 +7,14 @@
namespace Drupal\file;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\Core\Entity\DatabaseStorageControllerNG;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\Language;
/**
* File storage controller for files.
*/
class FileStorageController extends DatabaseStorageController {
class FileStorageController extends DatabaseStorageControllerNG {
/**
* Overrides Drupal\Core\Entity\DatabaseStorageController::create().
......@@ -37,8 +37,8 @@ public function create(array $values) {
*/
protected function preSave(EntityInterface $entity) {
$entity->timestamp = REQUEST_TIME;
$entity->filesize = filesize($entity->uri);
if (!isset($entity->langcode)) {
$entity->setSize(filesize($entity->getFileUri()));
if (!$entity->langcode->value) {
// Default the file's language code to none, because files are language
// neutral more often than language dependent. Until we have better
// flexible settings.
......@@ -55,7 +55,7 @@ public function preDelete($entities) {
// 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);
file_unmanaged_delete($entity->getFileUri());
}
// Delete corresponding file usage entries.
db_delete('file_usage')
......@@ -100,4 +100,64 @@ public function retrieveTemporaryFiles() {
':timestamp' => REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE
));
}
/**
* {@inheritdoc}
*/
public function baseFieldDefinitions() {
$properties['fid'] = array(
'label' => t('File ID'),
'description' => t('The file ID.'),
'type' => 'integer_field',
'read-only' => TRUE,
);
$properties['uuid'] = array(
'label' => t('UUID'),
'description' => t('The file UUID.'),
'type' => 'string_field',
'read-only' => TRUE,
);
$properties['langcode'] = array(
'label' => t('Language code'),
'description' => t('The file language code.'),
'type' => 'language_field',
);
$properties['uid'] = array(
'label' => t('User ID'),
'description' => t('The user ID of the file.'),
'type' => 'entity_reference_field',
'settings' => array('target_type' => 'user'),
);
$properties['filename'] = array(
'label' => t('Filename'),
'description' => t('Name of the file with no path components.'),
'type' => 'string_field',
);
$properties['uri'] = array(
'label' => t('URI'),
'description' => t('The URI to access the file (either local or remote).'),
'type' => 'string_field',
);
$properties['filemime'] = array(
'label' => t('File MIME type'),
'description' => t("The file's MIME type."),
'type' => 'string_field',
);
$properties['filesize'] = array(
'label' => t('File size'),
'description' => t('The size of the file in bytes.'),
'type' => 'boolean_field',
);
$properties['status'] = array(
'label' => t('Status'),
'description' => t('The status of the file, temporary (0) and permanent (1)'),
'type' => 'integer_field',
);
$properties['timestamp'] = array(
'label' => t('Created'),
'description' => t('The time that the node was created.'),
'type' => 'integer_field',
);
return $properties;
}
}
......@@ -51,7 +51,7 @@ public function __construct(Connection $connection, $table = 'file_usage') {
public function add(File $file, $module, $type, $id, $count = 1) {
$this->connection->merge($this->tableName)
->key(array(
'fid' => $file->fid,
'fid' => $file->id(),
'module' => $module,
'type' => $type,
'id' => $id,
......@@ -70,7 +70,7 @@ 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);
->condition('fid', $file->id());
if ($type && $id) {
$query
->condition('type', $type)
......@@ -85,7 +85,7 @@ public function delete(File $file, $module, $type = NULL, $id = NULL, $count = 1
if (!$result && $count > 0) {
$query = $this->connection->update($this->tableName)
->condition('module', $module)
->condition('fid', $file->fid);
->condition('fid', $file->id());
if ($type && $id) {
$query
->condition('type', $type)
......@@ -104,7 +104,7 @@ public function delete(File $file, $module, $type = NULL, $id = NULL, $count = 1
public function listUsage(File $file) {
$result = $this->connection->select($this->tableName, 'f')
->fields('f', array('module', 'type', 'id', 'count'))
->condition('fid', $file->fid)
->condition('fid', $file->id())
->condition('count', 0, '>')
->execute();
$references = array();
......
......@@ -19,8 +19,8 @@ abstract class FileUsageBase implements FileUsageInterface {
*/
public function add(File $file, $module, $type, $id, $count = 1) {
// Make sure that a used file is permament.
if ($file->status != FILE_STATUS_PERMANENT) {
$file->status = FILE_STATUS_PERMANENT;
if (!$file->isPermanent()) {
$file->setPermanent();
$file->save();
}
}
......@@ -33,7 +33,7 @@ public function delete(File $file, $module, $type = NULL, $id = NULL, $count = 1
// which result in a delete through system_cron().
$usage = file_usage()->listUsage($file);
if (empty($usage)) {
$file->status = 0;
$file->setTemporary();
$file->save();
}
}
......
......@@ -7,11 +7,12 @@
namespace Drupal\file\Plugin\Core\Entity;
use Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityNG;
use Drupal\Core\Entity\Annotation\EntityType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Language\Language;
use Drupal\file\FileInterface;
use Drupal\user\UserInterface;
/**
* Defines the file entity class.
......@@ -32,90 +33,129 @@
* }
* )
*/
class File extends Entity implements FileInterface {
class File extends EntityNG implements FileInterface {
/**
* The file ID.
* The plain data values of the contained properties.
*
* @var integer
* Define default values.
*
* @var array
*/
public $fid;
protected $values = array(
'langcode' => array(Language::LANGCODE_DEFAULT => array(0 => array('value' => Language::LANGCODE_NOT_SPECIFIED))),
);
/**
* The file UUID.
*
* @var string
* {@inheritdoc}
*/
public $uuid;
public function id() {
return $this->get('fid')->value;
}
/**
* The file language code.
*
* @var string
* {@inheritdoc}
*/
public $langcode = Language::LANGCODE_NOT_SPECIFIED;
public function getFilename() {
return $this->get('filename')->value;
}
/**
* The uid of the user who is associated with the file.
*
* @var integer
* {@inheritdoc}
*/
public $uid;
public function setFilename($filename) {
$this->get('filename')->value = $filename;
}
/**
* 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
* {@inheritdoc}
*/
public $filename;
public function getFileUri() {
return $this->get('uri')->value;
}
/**
* The URI to access the file (either local or remote).
*
* @var string
* {@inheritdoc}
*/
public $uri;
public function setFileUri($uri) {