Commit 988abc27 authored by catch's avatar catch

Issue #2045189 by jlindsey15: Move file entity dependent code in...

Issue #2045189 by jlindsey15: Move file entity dependent code in includes/file.inc and system.module to file.module.
parent bab8bdd9
......@@ -1015,243 +1015,7 @@ function file_unmanaged_delete_recursive($path, $callback = NULL) {
return file_unmanaged_delete($path);
}
/**
* Saves file uploads to a new location.
*
* The files will be added to the {file_managed} table as temporary files.
* Temporary files are periodically cleaned. Use file_usage()->add() to register
* the usage of the file which will automatically mark it as permanent.
*
* @param $form_field_name
* A string that is the associative array key of the upload form element in
* the form array.
* @param $validators
* An optional, associative array of callback functions used to validate the
* file. See file_validate() for a full discussion of the array format.
* If no extension validator is provided it will default to a limited safe
* list of extensions which is as follows: "jpg jpeg gif png txt
* doc xls pdf ppt pps odt ods odp". To allow all extensions you must
* explicitly set the 'file_validate_extensions' validator to an empty array
* (Beware: this is not safe and should only be allowed for trusted users, if
* at all).
* @param $destination
* A string containing the URI that the file should be copied to. This must
* be a stream wrapper URI. If this value is omitted, Drupal's temporary
* files scheme will be used ("temporary://").
* @param $delta
* Delta of the file to save or NULL to save all files. Defaults to NULL.
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE: Replace the existing file.
* - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
* unique.
* - FILE_EXISTS_ERROR: Do nothing and return FALSE.
*
* @return
* Function returns array of files or a single file object if $delta
* != NULL. Each file object contains the file information if the
* upload succeeded or FALSE in the event of an error. Function
* returns NULL if no file was uploaded.
*
* The documentation for the "File interface" group, which you can find under
* Related topics, or the header at the top of this file, documents the
* components of a file entity. In addition to the standard components,
* this function adds:
* - source: Path to the file before it is moved.
* - destination: Path to the file after it is moved (same as 'uri').
*/
function file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
global $user;
static $upload_cache;
// Make sure there's an upload to process.
if (empty($_FILES['files']['name'][$form_field_name])) {
return NULL;
}
// Return cached objects without processing since the file will have
// already been processed and the paths in $_FILES will be invalid.
if (isset($upload_cache[$form_field_name])) {
if (isset($delta)) {
return $upload_cache[$form_field_name][$delta];
}
return $upload_cache[$form_field_name];
}
// Prepare uploaded files info. Representation is slightly different
// for multiple uploads and we fix that here.
$uploaded_files = $_FILES;
if (!is_array($uploaded_files['files']['name'][$form_field_name])) {
foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $value)
$uploaded_files['files'][$value][$form_field_name] = array($uploaded_files['files'][$value][$form_field_name]);
}
$files = array();
foreach ($uploaded_files['files']['name'][$form_field_name] as $i => $name) {
// Check for file upload errors and return FALSE for this file if a lower
// level system error occurred. For a complete list of errors:
// See http://php.net/manual/features.file-upload.errors.php.
switch ($uploaded_files['files']['error'][$form_field_name][$i]) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $name, '%maxsize' => format_size(file_upload_max_size()))), 'error');
$files[$i] = FALSE;
continue;
case UPLOAD_ERR_PARTIAL:
case UPLOAD_ERR_NO_FILE:
drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $name)), 'error');
$files[$i] = FALSE;
continue;
case UPLOAD_ERR_OK:
// Final check that this is a valid upload, if it isn't, use the
// default error handler.
if (is_uploaded_file($uploaded_files['files']['tmp_name'][$form_field_name][$i])) {
break;
}
// Unknown error
default:
drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $name)), 'error');
$files[$i] = FALSE;
continue;
}
// Begin building file entity.
$values = array(
'uid' => $user->id(),
'status' => 0,
'filename' => trim(drupal_basename($name, '.')),
'uri' => $uploaded_files['files']['tmp_name'][$form_field_name][$i],
'filesize' => $uploaded_files['files']['size'][$form_field_name][$i],
);
$values['filemime'] = file_get_mimetype($values['filename']);
$file = entity_create('file', $values);
$extensions = '';
if (isset($validators['file_validate_extensions'])) {
if (isset($validators['file_validate_extensions'][0])) {
// Build the list of non-munged extensions if the caller provided them.
$extensions = $validators['file_validate_extensions'][0];
}
else {
// If 'file_validate_extensions' is set and the list is empty then the
// caller wants to allow any extension. In this case we have to remove the
// validator or else it will reject all extensions.
unset($validators['file_validate_extensions']);
}
}
else {
// No validator was provided, so add one using the default list.
// Build a default non-munged safe list for file_munge_filename().
$extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
$validators['file_validate_extensions'] = array();
$validators['file_validate_extensions'][0] = $extensions;
}
if (!empty($extensions)) {
// Munge the filename to protect against possible malicious extension
// hiding within an unknown file type (ie: filename.html.foo).
$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->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->getFilename())));
}
}
// If the destination is not provided, use the temporary directory.
if (empty($destination)) {
$destination = 'temporary://';
}
// Assert that the destination contains a valid stream.
$destination_scheme = file_uri_scheme($destination);
if (!file_stream_wrapper_valid_scheme($destination_scheme)) {
drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
$files[$i] = FALSE;
continue;
}
$file->source = $form_field_name;
// A file URI may already have a trailing slash or look like "public://".
if (substr($destination, -1) != '/') {
$destination .= '/';
}
$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) {
drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $form_field_name, '%directory' => $destination)), 'error');
$files[$i] = FALSE;
continue;
}
// Add in our check of the the file name length.
$validators['file_validate_name_length'] = array();
// Call the validation functions specified by this function's caller.
$errors = file_validate($file, $validators);
// Check for errors.
if (!empty($errors)) {
$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));
}
else {
$message .= ' ' . array_pop($errors);
}
form_set_error($form_field_name, $message);
$files[$i] = FALSE;
continue;
}
// Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
// 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->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;
continue;
}
// Set the permissions on the new file.
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->getFileUri()));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->id();
}
}
// If we made it this far it's safe to record this file in the database.
$file->save();
$files[$i] = $file;
}
// Add files to the cache.
$upload_cache[$form_field_name] = $files;
return isset($delta) ? $files[$delta] : $files;
}
/**
* Moves an uploaded file to a new location.
......
......@@ -721,6 +721,244 @@ function file_cron() {
}
}
/**
* Saves file uploads to a new location.
*
* The files will be added to the {file_managed} table as temporary files.
* Temporary files are periodically cleaned. Use file_usage()->add() to register
* the usage of the file which will automatically mark it as permanent.
*
* @param $form_field_name
* A string that is the associative array key of the upload form element in
* the form array.
* @param $validators
* An optional, associative array of callback functions used to validate the
* file. See file_validate() for a full discussion of the array format.
* If no extension validator is provided it will default to a limited safe
* list of extensions which is as follows: "jpg jpeg gif png txt
* doc xls pdf ppt pps odt ods odp". To allow all extensions you must
* explicitly set the 'file_validate_extensions' validator to an empty array
* (Beware: this is not safe and should only be allowed for trusted users, if
* at all).
* @param $destination
* A string containing the URI that the file should be copied to. This must
* be a stream wrapper URI. If this value is omitted, Drupal's temporary
* files scheme will be used ("temporary://").
* @param $delta
* Delta of the file to save or NULL to save all files. Defaults to NULL.
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE: Replace the existing file.
* - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
* unique.
* - FILE_EXISTS_ERROR: Do nothing and return FALSE.
*
* @return
* Function returns array of files or a single file object if $delta
* != NULL. Each file object contains the file information if the
* upload succeeded or FALSE in the event of an error. Function
* returns NULL if no file was uploaded.
*
* The documentation for the "File interface" group, which you can find under
* Related topics, or the header at the top of this file, documents the
* components of a file entity. In addition to the standard components,
* this function adds:
* - source: Path to the file before it is moved.
* - destination: Path to the file after it is moved (same as 'uri').
*/
function file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
global $user;
static $upload_cache;
// Make sure there's an upload to process.
if (empty($_FILES['files']['name'][$form_field_name])) {
return NULL;
}
// Return cached objects without processing since the file will have
// already been processed and the paths in $_FILES will be invalid.
if (isset($upload_cache[$form_field_name])) {
if (isset($delta)) {
return $upload_cache[$form_field_name][$delta];
}
return $upload_cache[$form_field_name];
}
// Prepare uploaded files info. Representation is slightly different
// for multiple uploads and we fix that here.
$uploaded_files = $_FILES;
if (!is_array($uploaded_files['files']['name'][$form_field_name])) {
foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $value)
$uploaded_files['files'][$value][$form_field_name] = array($uploaded_files['files'][$value][$form_field_name]);
}
$files = array();
foreach ($uploaded_files['files']['name'][$form_field_name] as $i => $name) {
// Check for file upload errors and return FALSE for this file if a lower
// level system error occurred. For a complete list of errors:
// See http://php.net/manual/features.file-upload.errors.php.
switch ($uploaded_files['files']['error'][$form_field_name][$i]) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $name, '%maxsize' => format_size(file_upload_max_size()))), 'error');
$files[$i] = FALSE;
continue;
case UPLOAD_ERR_PARTIAL:
case UPLOAD_ERR_NO_FILE:
drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $name)), 'error');
$files[$i] = FALSE;
continue;
case UPLOAD_ERR_OK:
// Final check that this is a valid upload, if it isn't, use the
// default error handler.
if (is_uploaded_file($uploaded_files['files']['tmp_name'][$form_field_name][$i])) {
break;
}
// Unknown error
default:
drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $name)), 'error');
$files[$i] = FALSE;
continue;
}
// Begin building file entity.
$values = array(
'uid' => $user->id(),
'status' => 0,
'filename' => trim(drupal_basename($name, '.')),
'uri' => $uploaded_files['files']['tmp_name'][$form_field_name][$i],
'filesize' => $uploaded_files['files']['size'][$form_field_name][$i],
);
$values['filemime'] = file_get_mimetype($values['filename']);
$file = entity_create('file', $values);
$extensions = '';
if (isset($validators['file_validate_extensions'])) {
if (isset($validators['file_validate_extensions'][0])) {
// Build the list of non-munged extensions if the caller provided them.
$extensions = $validators['file_validate_extensions'][0];
}
else {
// If 'file_validate_extensions' is set and the list is empty then the
// caller wants to allow any extension. In this case we have to remove the
// validator or else it will reject all extensions.
unset($validators['file_validate_extensions']);
}
}
else {
// No validator was provided, so add one using the default list.
// Build a default non-munged safe list for file_munge_filename().
$extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
$validators['file_validate_extensions'] = array();
$validators['file_validate_extensions'][0] = $extensions;
}
if (!empty($extensions)) {
// Munge the filename to protect against possible malicious extension
// hiding within an unknown file type (ie: filename.html.foo).
$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->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->getFilename())));
}
}
// If the destination is not provided, use the temporary directory.
if (empty($destination)) {
$destination = 'temporary://';
}
// Assert that the destination contains a valid stream.
$destination_scheme = file_uri_scheme($destination);
if (!file_stream_wrapper_valid_scheme($destination_scheme)) {
drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
$files[$i] = FALSE;
continue;
}
$file->source = $form_field_name;
// A file URI may already have a trailing slash or look like "public://".
if (substr($destination, -1) != '/') {
$destination .= '/';
}
$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) {
drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $form_field_name, '%directory' => $destination)), 'error');
$files[$i] = FALSE;
continue;
}
// Add in our check of the the file name length.
$validators['file_validate_name_length'] = array();
// Call the validation functions specified by this function's caller.
$errors = file_validate($file, $validators);
// Check for errors.
if (!empty($errors)) {
$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));
}
else {
$message .= ' ' . array_pop($errors);
}
form_set_error($form_field_name, $message);
$files[$i] = FALSE;
continue;
}
// Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
// 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->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;
continue;
}
// Set the permissions on the new file.
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->getFileUri()));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->id();
}
}
// If we made it this far it's safe to record this file in the database.
$file->save();
$files[$i] = $file;
}
// Add files to the cache.
$upload_cache[$form_field_name] = $files;
return isset($delta) ? $files[$delta] : $files;
}
/**
* Ajax callback: Processes file uploads and deletions.
*
......@@ -847,6 +1085,133 @@ function file_file_predelete(File $file) {
// TODO: Remove references to a file that is in-use.
}
/**
* Implements hook_tokens().
*/
function file_tokens($type, $tokens, array $data = array(), array $options = array()) {
$token_service = Drupal::token();
$url_options = array('absolute' => TRUE);
if (isset($options['langcode'])) {
$url_options['language'] = language_load($options['langcode']);
$langcode = $options['langcode'];
}
else {
$langcode = NULL;
}
$sanitize = !empty($options['sanitize']);
$replacements = array();
if ($type == 'file' && !empty($data['file'])) {
$file = $data['file'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Basic keys and values.
case 'fid':
$replacements[$original] = $file->id();
break;
// Essential file data
case 'name':
$replacements[$original] = $sanitize ? check_plain($file->getFilename()) : $file->getFilename();
break;
case 'path':
$replacements[$original] = $sanitize ? check_plain($file->getFileUri()) : $file->getFileUri();
break;
case 'mime':
$replacements[$original] = $sanitize ? check_plain($file->getMimeType()) : $file->getMimeType();
break;
case 'size':
$replacements[$original] = format_size($file->getSize());
break;
case 'url':
$replacements[$original] = $sanitize ? check_plain(file_create_url($file->getFileUri())) : file_create_url($file->getFileUri());
break;
// These tokens are default variations on the chained tokens handled below.
case 'timestamp':
$replacements[$original] = format_date($file->getChangedTime(), 'medium', '', NULL, $langcode);
break;
case 'owner':
$name = $file->getOwner()->label();
$replacements[$original] = $sanitize ? check_plain($name) : $name;
break;
}
}
if ($date_tokens = $token_service->findWithPrefix($tokens, 'timestamp')) {
$replacements += $token_service->generate('date', $date_tokens, array('date' => $file->getChangedTime()), $options);
}
if (($owner_tokens = $token_service->findWithPrefix($tokens, 'owner')) && $file->getOwner()) {
$replacements += $token_service->generate('user', $owner_tokens, array('user' => $file->getOwner()), $options);
}
}
return $replacements;
}
/**
* Implements hook_token_info().
*/
function file_token_info() {
$types['file'] = array(
'name' => t("Files"),
'description' => t("Tokens related to uploaded files."),
'needs-data' => 'file',
);
// File related tokens.
$file['fid'] = array(
'name' => t("File ID"),
'description' => t("The unique ID of the uploaded file."),
);
$file['name'] = array(
'name' => t("File name"),
'description' => t("The name of the file on disk."),
);
$file['path'] = array(
'name' => t("Path"),
'description' => t("The location of the file relative to Drupal root."),
);
$file['mime'] = array(
'name' => t("MIME type"),
'description' => t("The MIME type of the file."),
);
$file['size'] = array(
'name' => t("File size"),
'description' => t("The size of the file."),
);
$file['url'] = array(
'name' => t("URL"),
'description' => t("The web-accessible URL for the file."),
);
$file['timestamp'] = array(
'name' => t("Timestamp"),
'description' => t("The date the file was most recently changed."),
'type' => 'date',
);
$file['owner'] = array(
'name' => t("Owner"),
'description' => t("The user who originally uploaded the file."),
'type' => 'user',
);
return array(
'types' => $types,
'tokens' => array(
'file' => $file,
),
);
}
/**
* Render API callback: Expands the managed_file element type.
*
......
......@@ -4,8 +4,7 @@
* @file
* Builds placeholder replacement tokens system-wide data.
*
* This file handles tokens for the global 'site' token type, as well as
* 'date' and 'file' tokens.
* This file handles tokens for the global 'site' and 'date' tokens.
*/
/**
......@@ -20,11 +19,6 @@ function system_token_info() {
'name' => t("Dates"),
'description' => t("Tokens related to times and dates."),
);
$types['file'] = array(
'name' => t("Files"),
'description' => t("Tokens related to uploaded files."),
'needs-data' => 'file',
);
// Site-wide global tokens.
$site['name'] = array(
......@@ -78,49 +72,11 @@ function system_token_info() {
'description' => t("A date in UNIX timestamp format (%date)", array('%date' => REQUEST_TIME)),
);
// File related tokens.
$file['fid'] = array(
'name' => t("File ID"),
'description' => t("The unique ID of the uploaded file."),
);
$file['name'] = array(
'name' => t("File name"),
'description' => t("The name of the file on disk."),
);
$file['path'] = array(
'name' => t("Path"),
'description' => t("The location of the file relative to Drupal root."),
);
$file['mime'] = array(
'name' => t("MIME type"),
'description' => t("The MIME type of the file."),
);
$file['size'] = array(
'name' => t("File size"),
'description' => t("The size of the file."),
);
$file['url'] = array(
'name' => t("URL"),
'description' => t("The web-accessible URL for the file."),
);
$file['timestamp'] = array(
'name' => t("Timestamp"),
'description' => t("The date the file was most recently changed."),
'type' => 'date',
);
$file['owner'] = array(
'name' => t("Owner"),
'description' => t("The user who originally uploaded the file."),
'type' => 'user',
);
return array(
'types' => $types,
'tokens' => array(
'site' => $site,
'date' => $date,
'file' => $file,
),
);
}
......@@ -214,57 +170,5 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
}
}
elseif ($type == 'file' && !empty($data['file'])) {
$file = $data['file'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Basic keys and values.
case 'fid':
$replacements[$original] = $file->id();
break;
// Essential file data
case 'name':
$replacements[$original] = $sanitize ? check_plain($file->getFilename()) : $file->getFilename();
break;
case 'path':
$replacements[$original] = $sanitize ? check_plain($file->getFileUri()) : $file->getFileUri();
break;
case 'mime':
$replacements[$original] = $sanitize ? check_plain($file->getMimeType()) : $file->getMimeType();
break;
case 'size':
$replacements[$original] = format_size($file->getSize());
break;