Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,6 @@
name: 'File test'
type: module
description: 'Provides hooks for testing File module functionality.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,10 @@
file_module_test.managed_test:
path: '/file/test/{tree}/{extended}/{multiple}/{default_fids}'
defaults:
_form: '\Drupal\file_module_test\Form\FileModuleTestForm'
tree: TRUE
extended: TRUE
multiple: FALSE
default_fids: NULL
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,91 @@
<?php
namespace Drupal\file_module_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for file_module_test module.
*
* @internal
*/
class FileModuleTestForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'file_module_test_form';
}
/**
* {@inheritdoc}
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param bool $tree
* (optional) If the form should use #tree. Defaults to TRUE.
* @param bool $extended
* (optional) If the form should use #extended. Defaults to TRUE.
* @param bool $multiple
* (optional) If the form should use #multiple. Defaults to FALSE.
* @param array $default_fids
* (optional) Any default file IDs to use.
*/
public function buildForm(array $form, FormStateInterface $form_state, $tree = TRUE, $extended = TRUE, $multiple = FALSE, $default_fids = NULL) {
$form['#tree'] = (bool) $tree;
$form['nested']['file'] = [
'#type' => 'managed_file',
'#title' => $this->t('Managed <em>@type</em>', ['@type' => 'file & butter']),
'#upload_location' => 'public://test',
'#progress_message' => $this->t('Please wait...'),
'#extended' => (bool) $extended,
'#size' => 13,
'#multiple' => (bool) $multiple,
];
if ($default_fids) {
$default_fids = explode(',', $default_fids);
$form['nested']['file']['#default_value'] = $extended ? ['fids' => $default_fids] : $default_fids;
}
$form['textfield'] = [
'#type' => 'textfield',
'#title' => $this->t('Type a value and ensure it stays'),
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form['#tree']) {
$uploads = $form_state->getValue(['nested', 'file']);
}
else {
$uploads = $form_state->getValue('file');
}
if ($form['nested']['file']['#extended']) {
$uploads = $uploads['fids'];
}
$fids = [];
foreach ($uploads as $fid) {
$fids[] = $fid;
}
\Drupal::messenger()->addStatus($this->t('The file ids are %fids.', ['%fids' => implode(',', $fids)]));
}
}

View file

@ -0,0 +1,6 @@
name: 'File test'
type: module
description: 'Support module for file handling tests.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,351 @@
<?php
/**
* @file
* Helper module for the file tests.
*
* The caller is must call file_test_reset() to initializing this module before
* calling file_test_get_calls() or file_test_set_return().
*/
use Drupal\file\Entity\File;
const FILE_URL_TEST_CDN_1 = 'http://cdn1.example.com';
const FILE_URL_TEST_CDN_2 = 'http://cdn2.example.com';
/**
* Reset/initialize the history of calls to the file_* hooks.
*
* @see file_test_get_calls()
* @see file_test_reset()
*/
function file_test_reset() {
// Keep track of calls to these hooks
$results = [
'load' => [],
'validate' => [],
'download' => [],
'insert' => [],
'update' => [],
'copy' => [],
'move' => [],
'delete' => [],
];
\Drupal::state()->set('file_test.results', $results);
// These hooks will return these values, see file_test_set_return().
$return = [
'validate' => [],
'download' => NULL,
];
\Drupal::state()->set('file_test.return', $return);
}
/**
* Get the arguments passed to invocation of a given hook since
* file_test_reset() was last called.
*
* @param string $op
* One of the hook_file_* operations: 'load', 'validate', 'download',
* 'insert', 'update', 'copy', 'move', 'delete'.
*
* @return array
* Array of the parameters passed to each call.
*
* @see _file_test_log_call()
* @see file_test_reset()
*/
function file_test_get_calls($op) {
$results = \Drupal::state()->get('file_test.results') ?: [];
return $results[$op];
}
/**
* Get an array with the calls for all hooks.
*
* @return
* An array keyed by hook name ('load', 'validate', 'download', 'insert',
* 'update', 'copy', 'move', 'delete') with values being arrays of parameters
* passed to each call.
*/
function file_test_get_all_calls() {
return \Drupal::state()->get('file_test.results') ?: [];
}
/**
* Store the values passed to a hook invocation.
*
* @param string $op
* One of the hook_file_* operations: 'load', 'validate', 'download',
* 'insert', 'update', 'copy', 'move', 'delete'.
* @param array $args
* Values passed to hook.
*
* @see file_test_get_calls()
* @see file_test_reset()
*/
function _file_test_log_call($op, $args) {
if (\Drupal::state()->get('file_test.count_hook_invocations', TRUE)) {
$results = \Drupal::state()->get('file_test.results') ?: [];
$results[$op][] = $args;
\Drupal::state()->set('file_test.results', $results);
}
}
/**
* Load the appropriate return value.
*
* @param string $op
* One of the hook_file_[validate,download] operations.
*
* @return mixed
* Value set by file_test_set_return().
*
* @see file_test_set_return()
* @see file_test_reset()
*/
function _file_test_get_return($op) {
$return = \Drupal::state()->get('file_test.return') ?: [$op => NULL];
return $return[$op];
}
/**
* Assign a return value for a given operation.
*
* @param string $op
* One of the hook_file_[validate,download] operations.
* @param mixed $value
* Value for the hook to return.
*
* @see _file_test_get_return()
* @see file_test_reset()
*/
function file_test_set_return($op, $value) {
$return = \Drupal::state()->get('file_test.return') ?: [];
$return[$op] = $value;
\Drupal::state()->set('file_test.return', $return);
}
/**
* Implements hook_ENTITY_TYPE_load() for file entities.
*/
function file_test_file_load($files) {
foreach ($files as $file) {
_file_test_log_call('load', [$file->id()]);
// Assign a value on the object so that we can test that the $file is passed
// by reference.
$file->file_test['loaded'] = TRUE;
}
}
/**
* Implements hook_file_validate().
*/
function file_test_file_validate(File $file) {
_file_test_log_call('validate', [$file->id()]);
return _file_test_get_return('validate');
}
/**
* Implements hook_file_download().
*/
function file_test_file_download($uri) {
if (\Drupal::state()->get('file_test.allow_all', FALSE)) {
$files = entity_load_multiple_by_properties('file', ['uri' => $uri]);
$file = reset($files);
return file_get_content_headers($file);
}
_file_test_log_call('download', [$uri]);
return _file_test_get_return('download');
}
/**
* Implements hook_ENTITY_TYPE_insert() for file entities.
*/
function file_test_file_insert(File $file) {
_file_test_log_call('insert', [$file->id()]);
}
/**
* Implements hook_ENTITY_TYPE_update() for file entities.
*/
function file_test_file_update(File $file) {
_file_test_log_call('update', [$file->id()]);
}
/**
* Implements hook_file_copy().
*/
function file_test_file_copy(File $file, $source) {
_file_test_log_call('copy', [$file->id(), $source->id()]);
}
/**
* Implements hook_file_move().
*/
function file_test_file_move(File $file, File $source) {
_file_test_log_call('move', [$file->id(), $source->id()]);
}
/**
* Implements hook_ENTITY_TYPE_predelete() for file entities.
*/
function file_test_file_predelete(File $file) {
_file_test_log_call('delete', [$file->id()]);
}
/**
* Implements hook_file_url_alter().
*/
function file_test_file_url_alter(&$uri) {
// Only run this hook when this variable is set. Otherwise, we'd have to add
// another hidden test module just for this hook.
$alter_mode = \Drupal::state()->get('file_test.hook_file_url_alter');
if (!$alter_mode) {
return;
}
// Test alteration of file URLs to use a CDN.
elseif ($alter_mode == 'cdn') {
$cdn_extensions = ['css', 'js', 'gif', 'jpg', 'jpeg', 'png'];
// Most CDNs don't support private file transfers without a lot of hassle,
// so don't support this in the common case.
$schemes = ['public'];
$scheme = file_uri_scheme($uri);
// Only serve shipped files and public created files from the CDN.
if (!$scheme || in_array($scheme, $schemes)) {
// Shipped files.
if (!$scheme) {
$path = $uri;
}
// Public created files.
else {
$wrapper = \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
$path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
}
// Clean up Windows paths.
$path = str_replace('\\', '/', $path);
// Serve files with one of the CDN extensions from CDN 1, all others from
// CDN 2.
$pathinfo = pathinfo($path);
if (array_key_exists('extension', $pathinfo) && in_array($pathinfo['extension'], $cdn_extensions)) {
$uri = FILE_URL_TEST_CDN_1 . '/' . $path;
}
else {
$uri = FILE_URL_TEST_CDN_2 . '/' . $path;
}
}
}
// Test alteration of file URLs to use root-relative URLs.
elseif ($alter_mode == 'root-relative') {
// Only serve shipped files and public created files with root-relative
// URLs.
$scheme = file_uri_scheme($uri);
if (!$scheme || $scheme == 'public') {
// Shipped files.
if (!$scheme) {
$path = $uri;
}
// Public created files.
else {
$wrapper = \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
$path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
}
// Clean up Windows paths.
$path = str_replace('\\', '/', $path);
// Generate a root-relative URL.
$uri = base_path() . '/' . $path;
}
}
// Test alteration of file URLs to use protocol-relative URLs.
elseif ($alter_mode == 'protocol-relative') {
// Only serve shipped files and public created files with protocol-relative
// URLs.
$scheme = file_uri_scheme($uri);
if (!$scheme || $scheme == 'public') {
// Shipped files.
if (!$scheme) {
$path = $uri;
}
// Public created files.
else {
$wrapper = \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
$path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
}
// Clean up Windows paths.
$path = str_replace('\\', '/', $path);
// Generate a protocol-relative URL.
$uri = '/' . base_path() . '/' . $path;
}
}
}
/**
* Implements hook_file_mimetype_mapping_alter().
*/
function file_test_file_mimetype_mapping_alter(&$mapping) {
// Add new mappings.
$mapping['mimetypes']['file_test_mimetype_1'] = 'madeup/file_test_1';
$mapping['mimetypes']['file_test_mimetype_2'] = 'madeup/file_test_2';
$mapping['mimetypes']['file_test_mimetype_3'] = 'madeup/doc';
$mapping['extensions']['file_test_1'] = 'file_test_mimetype_1';
$mapping['extensions']['file_test_2'] = 'file_test_mimetype_2';
$mapping['extensions']['file_test_3'] = 'file_test_mimetype_2';
// Override existing mapping.
$mapping['extensions']['doc'] = 'file_test_mimetype_3';
}
/**
* Helper validator that returns the $errors parameter.
*/
function file_test_validator(File $file, $errors) {
return $errors;
}
/**
* Helper function for testing file_scan_directory().
*
* Each time the function is called the file is stored in a static variable.
* When the function is called with no $filepath parameter, the results are
* returned.
*
* @param string|null $filepath
* File path
* @return array
* If $filepath is NULL, an array of all previous $filepath parameters
*/
function file_test_file_scan_callback($filepath = NULL) {
$files = &drupal_static(__FUNCTION__, []);
if (isset($filepath)) {
$files[] = $filepath;
}
else {
return $files;
}
}
/**
* Reset static variables used by file_test_file_scan_callback().
*/
function file_test_file_scan_callback_reset() {
drupal_static_reset('file_test_file_scan_callback');
}
/**
* Implements hook_entity_info_alter().
*/
function file_test_entity_type_alter(&$entity_types) {
if (\Drupal::state()->get('file_test_alternate_access_handler', FALSE)) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
$entity_types['file']
->setAccessClass('Drupal\file_test\FileTestAccessControlHandler');
}
}

View file

@ -0,0 +1,12 @@
file.test:
path: '/file-test/upload'
defaults:
_form: 'Drupal\file_test\Form\FileTestForm'
requirements:
_access: 'TRUE'
file.save_upload_from_form_test:
path: '/file-test/save_upload_from_form_test'
defaults:
_form: 'Drupal\file_test\Form\FileTestSaveUploadFromForm'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,13 @@
services:
stream_wrapper.dummy_readonly:
class: Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper
tags:
- { name: stream_wrapper, scheme: dummy-readonly }
stream_wrapper.dummy_remote:
class: Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper
tags:
- { name: stream_wrapper, scheme: dummy-remote }
stream_wrapper.dummy:
class: Drupal\file_test\StreamWrapper\DummyStreamWrapper
tags:
- { name: stream_wrapper, scheme: dummy }

View file

@ -0,0 +1,23 @@
<?php
namespace Drupal\file_test;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\file\FileAccessFormatterControlHandlerInterface;
use Drupal\file\FileAccessControlHandler;
/**
* Defines a class for an alternate file access control handler.
*/
class FileTestAccessControlHandler extends FileAccessControlHandler implements FileAccessFormatterControlHandlerInterface {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
\Drupal::state()->set('file_access_formatter_check', TRUE);
return parent::checkAccess($entity, $operation, $account);
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Drupal\file_test\Form;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* File test form class.
*/
class FileTestForm implements FormInterface {
/**
* {@inheritdoc}
*/
public function getFormId() {
return '_file_test_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['file_test_upload'] = [
'#type' => 'file',
'#title' => t('Upload a file'),
];
$form['file_test_replace'] = [
'#type' => 'select',
'#title' => t('Replace existing image'),
'#options' => [
FILE_EXISTS_RENAME => t('Appends number until name is unique'),
FILE_EXISTS_REPLACE => t('Replace the existing file'),
FILE_EXISTS_ERROR => t('Fail with an error'),
],
'#default_value' => FILE_EXISTS_RENAME,
];
$form['file_subdir'] = [
'#type' => 'textfield',
'#title' => t('Subdirectory for test file'),
'#default_value' => '',
];
$form['extensions'] = [
'#type' => 'textfield',
'#title' => t('Allowed extensions.'),
'#default_value' => '',
];
$form['allow_all_extensions'] = [
'#type' => 'checkbox',
'#title' => t('Allow all extensions?'),
'#default_value' => FALSE,
];
$form['is_image_file'] = [
'#type' => 'checkbox',
'#title' => t('Is this an image file?'),
'#default_value' => TRUE,
];
$form['submit'] = [
'#type' => 'submit',
'#value' => t('Submit'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Process the upload and perform validation. Note: we're using the
// form value for the $replace parameter.
if (!$form_state->isValueEmpty('file_subdir')) {
$destination = 'temporary://' . $form_state->getValue('file_subdir');
file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
}
else {
$destination = FALSE;
}
// Setup validators.
$validators = [];
if ($form_state->getValue('is_image_file')) {
$validators['file_validate_is_image'] = [];
}
if ($form_state->getValue('allow_all_extensions')) {
$validators['file_validate_extensions'] = [];
}
elseif (!$form_state->isValueEmpty('extensions')) {
$validators['file_validate_extensions'] = [$form_state->getValue('extensions')];
}
// The test for drupal_move_uploaded_file() triggering a warning is
// unavoidable. We're interested in what happens afterwards in
// file_save_upload().
if (\Drupal::state()->get('file_test.disable_error_collection')) {
define('SIMPLETEST_COLLECT_ERRORS', FALSE);
}
$file = file_save_upload('file_test_upload', $validators, $destination, 0, $form_state->getValue('file_test_replace'));
if ($file) {
$form_state->setValue('file_test_upload', $file);
\Drupal::messenger()->addStatus(t('File @filepath was uploaded.', ['@filepath' => $file->getFileUri()]));
\Drupal::messenger()->addStatus(t('File name is @filename.', ['@filename' => $file->getFilename()]));
\Drupal::messenger()->addStatus(t('File MIME type is @mimetype.', ['@mimetype' => $file->getMimeType()]));
\Drupal::messenger()->addStatus(t('You WIN!'));
}
elseif ($file === FALSE) {
\Drupal::messenger()->addError(t('Epic upload FAIL!'));
}
}
}

View file

@ -0,0 +1,179 @@
<?php
namespace Drupal\file_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\State\StateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* File test form class.
*/
class FileTestSaveUploadFromForm extends FormBase {
/**
* Stores the state storage service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a FileTestSaveUploadFromForm object.
*
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(StateInterface $state, MessengerInterface $messenger) {
$this->state = $state;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('state'),
$container->get('messenger')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return '_file_test_save_upload_from_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['file_test_upload'] = [
'#type' => 'file',
'#multiple' => TRUE,
'#title' => $this->t('Upload a file'),
];
$form['file_test_replace'] = [
'#type' => 'select',
'#title' => $this->t('Replace existing image'),
'#options' => [
FILE_EXISTS_RENAME => $this->t('Appends number until name is unique'),
FILE_EXISTS_REPLACE => $this->t('Replace the existing file'),
FILE_EXISTS_ERROR => $this->t('Fail with an error'),
],
'#default_value' => FILE_EXISTS_RENAME,
];
$form['file_subdir'] = [
'#type' => 'textfield',
'#title' => $this->t('Subdirectory for test file'),
'#default_value' => '',
];
$form['extensions'] = [
'#type' => 'textfield',
'#title' => $this->t('Allowed extensions.'),
'#default_value' => '',
];
$form['allow_all_extensions'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow all extensions?'),
'#default_value' => FALSE,
];
$form['is_image_file'] = [
'#type' => 'checkbox',
'#title' => $this->t('Is this an image file?'),
'#default_value' => TRUE,
];
$form['error_message'] = [
'#type' => 'textfield',
'#title' => $this->t('Custom error message.'),
'#default_value' => '',
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Process the upload and perform validation. Note: we're using the
// form value for the $replace parameter.
if (!$form_state->isValueEmpty('file_subdir')) {
$destination = 'temporary://' . $form_state->getValue('file_subdir');
file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
}
else {
$destination = FALSE;
}
// Preset custom error message if requested.
if ($form_state->getValue('error_message')) {
$this->messenger->addError($form_state->getValue('error_message'));
}
// Setup validators.
$validators = [];
if ($form_state->getValue('is_image_file')) {
$validators['file_validate_is_image'] = [];
}
if ($form_state->getValue('allow_all_extensions')) {
$validators['file_validate_extensions'] = [];
}
elseif (!$form_state->isValueEmpty('extensions')) {
$validators['file_validate_extensions'] = [$form_state->getValue('extensions')];
}
// The test for drupal_move_uploaded_file() triggering a warning is
// unavoidable. We're interested in what happens afterwards in
// _file_save_upload_from_form().
if ($this->state->get('file_test.disable_error_collection')) {
define('SIMPLETEST_COLLECT_ERRORS', FALSE);
}
$form['file_test_upload']['#upload_validators'] = $validators;
$form['file_test_upload']['#upload_location'] = $destination;
$this->messenger->addStatus($this->t('Number of error messages before _file_save_upload_from_form(): @count.', ['@count' => count($this->messenger->messagesByType(MessengerInterface::TYPE_ERROR))]));
$file = _file_save_upload_from_form($form['file_test_upload'], $form_state, 0, $form_state->getValue('file_test_replace'));
$this->messenger->addStatus($this->t('Number of error messages after _file_save_upload_from_form(): @count.', ['@count' => count($this->messenger->messagesByType(MessengerInterface::TYPE_ERROR))]));
if ($file) {
$form_state->setValue('file_test_upload', $file);
$this->messenger->addStatus($this->t('File @filepath was uploaded.', ['@filepath' => $file->getFileUri()]));
$this->messenger->addStatus($this->t('File name is @filename.', ['@filename' => $file->getFilename()]));
$this->messenger->addStatus($this->t('File MIME type is @mimetype.', ['@mimetype' => $file->getMimeType()]));
$this->messenger->addStatus($this->t('You WIN!'));
}
elseif ($file === FALSE) {
$this->messenger->addError($this->t('Epic upload FAIL!'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\file_test\StreamWrapper;
use Drupal\Core\StreamWrapper\LocalReadOnlyStream;
/**
* Helper class for testing the stream wrapper registry.
*
* Dummy stream wrapper implementation (dummy-readonly://).
*/
class DummyReadOnlyStreamWrapper extends LocalReadOnlyStream {
/**
* {@inheritdoc}
*/
public function getName() {
return t('Dummy files (readonly)');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Dummy wrapper for simpletest (readonly).');
}
public function getDirectoryPath() {
return \Drupal::service('site.path') . '/files';
}
/**
* Override getInternalUri().
*
* Return a dummy path for testing.
*/
public function getInternalUri() {
return '/dummy/example.txt';
}
/**
* Override getExternalUrl().
*
* Return the HTML URI of a public file.
*/
public function getExternalUrl() {
return '/dummy/example.txt';
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\file_test\StreamWrapper;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
/**
* Dummy read-only remote stream wrapper (dummy-remote-readonly://).
*/
class DummyRemoteReadOnlyStreamWrapper extends DummyRemoteStreamWrapper {
/**
* {@inheritdoc}
*/
public static function getType() {
return StreamWrapperInterface::READ_VISIBLE;
}
/**
* {@inheritdoc}
*/
public function getName() {
return t('Dummy remote read-only files');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Dummy remote read-only stream wrapper for testing.');
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\file_test\StreamWrapper;
use Drupal\Core\StreamWrapper\PublicStream;
/**
* Helper class for testing the stream wrapper registry.
*
* Dummy remote stream wrapper implementation (dummy-remote://).
*
* Basically just the public scheme but not returning a local file for realpath.
*/
class DummyRemoteStreamWrapper extends PublicStream {
/**
* {@inheritdoc}
*/
public function getName() {
return t('Dummy files (remote)');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Dummy wrapper for simpletest (remote).');
}
public function realpath() {
return FALSE;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\file_test\StreamWrapper;
use Drupal\Core\StreamWrapper\LocalStream;
/**
* Helper class for testing the stream wrapper registry.
*
* Dummy stream wrapper implementation (dummy://).
*/
class DummyStreamWrapper extends LocalStream {
/**
* {@inheritdoc}
*/
public function getName() {
return t('Dummy files');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Dummy wrapper for simpletest.');
}
public function getDirectoryPath() {
return \Drupal::service('site.path') . '/files';
}
/**
* Override getInternalUri().
*
* Return a dummy path for testing.
*/
public function getInternalUri() {
return '/dummy/example.txt';
}
/**
* Override getExternalUrl().
*
* Return the HTML URI of a public file.
*/
public function getExternalUrl() {
return '/dummy/example.txt';
}
}

View file

@ -0,0 +1,71 @@
uuid: 9ca49b35-b49d-4014-9337-965cdf15b61e
langcode: en
status: true
dependencies:
config:
- field.field.node.article.body
- field.field.node.article.comment
- field.field.node.article.field_file_generic_2677990
- field.field.node.article.field_file_table_2677990
- field.field.node.article.field_image
- field.field.node.article.field_tags
- image.style.large
- node.type.article
module:
- comment
- file
- image
- text
- user
_core:
default_config_hash: JtAg_-waIt1quMtdDtHIaXJMxvTuSmxW7bWyO6Zd68E
id: node.article.default
targetEntityType: node
bundle: article
mode: default
content:
body:
type: text_default
weight: 0
settings: { }
third_party_settings: { }
label: hidden
comment:
label: above
type: comment_default
weight: 20
settings:
pager_id: 0
third_party_settings: { }
field_file_generic_2677990:
weight: 101
label: above
settings: { }
third_party_settings: { }
type: file_default
field_file_table_2677990:
weight: 102
label: above
settings: { }
third_party_settings: { }
type: file_table
field_image:
type: image
weight: -1
settings:
image_style: large
image_link: ''
third_party_settings: { }
label: hidden
field_tags:
type: entity_reference_label
weight: 10
label: above
settings:
link: true
third_party_settings: { }
links:
weight: 100
settings: { }
third_party_settings: { }
hidden: { }

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
* upgrade path of https://www.drupal.org/node/2677990.
*/
use Drupal\Core\Database\Database;
use Drupal\Component\Serialization\Yaml;
use Drupal\field\Entity\FieldStorageConfig;
$connection = Database::getConnection();
// Configuration for a file field storage for generic display.
$field_file_generic_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.storage.node.field_file_generic_2677990.yml'));
// Configuration for a file field storage for table display.
$field_file_table_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.storage.node.field_file_table_2677990.yml'));
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'field.storage.' . $field_file_generic_2677990['id'],
'data' => serialize($field_file_generic_2677990),
])
->values([
'collection' => '',
'name' => 'field.storage.' . $field_file_table_2677990['id'],
'data' => serialize($field_file_table_2677990),
])
->execute();
// We need to Update the registry of "last installed" field definitions.
$installed = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'entity.definitions.installed')
->condition('name', 'node.field_storage_definitions')
->execute()
->fetchField();
$installed = unserialize($installed);
$installed['field_file_generic_2677990'] = new FieldStorageConfig($field_file_generic_2677990);
$installed['field_file_table_2677990'] = new FieldStorageConfig($field_file_table_2677990);
$connection->update('key_value')
->condition('collection', 'entity.definitions.installed')
->condition('name', 'node.field_storage_definitions')
->fields([
'value' => serialize($installed),
])
->execute();
// Configuration for a file field storage for generic display.
$field_file_generic_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.field.node.article.field_file_generic_2677990.yml'));
// Configuration for a file field storage for table display.
$field_file_table_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.field.node.article.field_file_table_2677990.yml'));
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'field.field.' . $field_file_generic_2677990['id'],
'data' => serialize($field_file_generic_2677990),
])
->values([
'collection' => '',
'name' => 'field.field.' . $field_file_table_2677990['id'],
'data' => serialize($field_file_table_2677990),
])
->execute();
// Configuration of the view mode to set the proper formatters.
$view_mode_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/core.entity_view_display.node.article.default_2677990.yml'));
$connection->update('config')
->fields([
'data' => serialize($view_mode_2677990),
])
->condition('name', 'core.entity_view_display.' . $view_mode_2677990['id'])
->execute();

View file

@ -0,0 +1,27 @@
uuid: d352a831-c267-4ecc-9b4e-7ae1896b2241
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_file_generic_2677990
- node.type.article
module:
- file
id: node.article.field_file_generic_2677990
field_name: field_file_generic_2677990
entity_type: node
bundle: article
label: 'File generic'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
file_directory: '[date:custom:Y]-[date:custom:m]'
file_extensions: txt
max_filesize: ''
description_field: true
handler: 'default:file'
handler_settings: { }
field_type: file

View file

@ -0,0 +1,27 @@
uuid: 44c7a590-ffcc-4534-b01c-b17b8d57ed48
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_file_table_2677990
- node.type.article
module:
- file
id: node.article.field_file_table_2677990
field_name: field_file_table_2677990
entity_type: node
bundle: article
label: 'File table'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
file_directory: '[date:custom:Y]-[date:custom:m]'
file_extensions: txt
max_filesize: ''
description_field: true
handler: 'default:file'
handler_settings: { }
field_type: file

View file

@ -0,0 +1,23 @@
uuid: a7eb470d-e538-4221-a8ac-57f989d92d8e
langcode: en
status: true
dependencies:
module:
- file
- node
id: node.field_file_generic_2677990
field_name: field_file_generic_2677990
entity_type: node
type: file
settings:
display_field: false
display_default: false
uri_scheme: public
target_type: file
module: file
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View file

@ -0,0 +1,23 @@
uuid: 43113a5e-3e07-4234-b045-4aa4cd217dca
langcode: en
status: true
dependencies:
module:
- file
- node
id: node.field_file_table_2677990
field_name: field_file_table_2677990
entity_type: node
type: file
settings:
display_field: false
display_default: false
uri_scheme: public
target_type: file
module: file
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View file

@ -0,0 +1,9 @@
name: 'File test views'
type: module
description: 'Provides default views for views file tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- drupal:file
- drupal:views

View file

@ -0,0 +1,48 @@
langcode: en
status: true
dependencies: { }
id: file_extension_view
label: 'Test view for file extension views field handler'
module: views
description: ''
tag: ''
base_table: file_managed
base_field: fid
core: '8'
display:
default:
display_options:
defaults:
fields: false
pager: false
sorts: false
fields:
fid:
field: fid
id: fid
relationship: none
table: file_managed
plugin_id: field
extension:
field: extension
id: extension
relationship: none
table: file_managed
plugin_id: field
type: file_extension
pager:
options:
offset: 0
type: none
sorts:
fid:
field: fid
id: fid
order: ASC
relationship: none
table: file_managed
plugin_id: standard
display_plugin: default
display_title: Master
id: default
position: 0

View file

@ -0,0 +1,77 @@
langcode: en
status: true
dependencies:
module:
- file
- user
id: test_file_user_file_data
label: test_file_user_file_data
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access user profiles'
cache:
type: tag
style:
type: table
options:
grouping: { }
row_class: ''
default_row_class: true
override: true
sticky: false
caption: ''
summary: ''
description: ''
columns:
name: name
fid: fid
info:
name:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
fid:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
default: '-1'
empty_table: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
relationships:
user_file_target_id:
id: user_file_target_id
table: user__user_file
field: user_file_target_id
relationship: none
group_type: group
admin_label: 'file from user_file'
required: true
plugin_id: standard
arguments: { }
display_extenders: { }

View file

@ -0,0 +1,168 @@
<?php
namespace Drupal\Tests\file\Functional;
/**
* Tests for download/file transfer functions.
*
* @group file
*/
class DownloadTest extends FileManagedTestBase {
protected function setUp() {
parent::setUp();
// Clear out any hook calls.
file_test_reset();
}
/**
* Test the public file transfer system.
*/
public function testPublicFileTransfer() {
// Test generating a URL to a created file.
$file = $this->createFile();
$url = file_create_url($file->getFileUri());
// URLs can't contain characters outside the ASCII set so $filename has to be
// encoded.
$filename = $GLOBALS['base_url'] . '/' . \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath() . '/' . rawurlencode($file->getFilename());
$this->assertEqual($filename, $url, 'Correctly generated a URL for a created file.');
$http_client = $this->getHttpClient();
$response = $http_client->head($url);
$this->assertEquals(200, $response->getStatusCode(), 'Confirmed that the generated URL is correct by downloading the created file.');
// Test generating a URL to a shipped file (i.e. a file that is part of
// Drupal core, a module or a theme, for example a JavaScript file).
$filepath = 'core/assets/vendor/jquery/jquery.min.js';
$url = file_create_url($filepath);
$this->assertEqual($GLOBALS['base_url'] . '/' . $filepath, $url, 'Correctly generated a URL for a shipped file.');
$response = $http_client->head($url);
$this->assertEquals(200, $response->getStatusCode(), 'Confirmed that the generated URL is correct by downloading the shipped file.');
}
/**
* Test the private file transfer system.
*/
public function testPrivateFileTransferWithoutPageCache() {
$this->doPrivateFileTransferTest();
}
/**
* Test the private file transfer system.
*/
protected function doPrivateFileTransferTest() {
// Set file downloads to private so handler functions get called.
// Create a file.
$contents = $this->randomMachineName(8);
$file = $this->createFile(NULL, $contents, 'private');
// Created private files without usage are by default not accessible
// for a user different from the owner, but createFile always uses uid 1
// as the owner of the files. Therefore make it permanent to allow access
// if a module allows it.
$file->setPermanent();
$file->save();
$url = file_create_url($file->getFileUri());
// Set file_test access header to allow the download.
file_test_set_return('download', ['x-foo' => 'Bar']);
$this->drupalGet($url);
$this->assertEqual($this->drupalGetHeader('x-foo'), 'Bar', 'Found header set by file_test module on private download.');
$this->assertFalse($this->drupalGetHeader('x-drupal-cache'), 'Page cache is disabled on private file download.');
$this->assertResponse(200, 'Correctly allowed access to a file when file_test provides headers.');
// Test that the file transferred correctly.
$this->assertSame($contents, $this->getSession()->getPage()->getContent(), 'Contents of the file are correct.');
$http_client = $this->getHttpClient();
// Deny access to all downloads via a -1 header.
file_test_set_return('download', -1);
$response = $http_client->head($url, ['http_errors' => FALSE]);
$this->assertSame(403, $response->getStatusCode(), 'Correctly denied access to a file when file_test sets the header to -1.');
// Try non-existent file.
$url = file_create_url('private://' . $this->randomMachineName());
$response = $http_client->head($url, ['http_errors' => FALSE]);
$this->assertSame(404, $response->getStatusCode(), 'Correctly returned 404 response for a non-existent file.');
}
/**
* Test file_create_url().
*/
public function testFileCreateUrl() {
// Tilde (~) is excluded from this test because it is encoded by
// rawurlencode() in PHP 5.2 but not in PHP 5.3, as per RFC 3986.
// @see http://php.net/manual/function.rawurlencode.php#86506
// "Special" ASCII characters.
$basename = " -._!$'\"()*@[]?&+%#,;=:\n\x00" .
// Characters that look like a percent-escaped string.
"%23%25%26%2B%2F%3F" .
// Characters from various non-ASCII alphabets.
"éøïвβ中國書۞";
$basename_encoded = '%20-._%21%24%27%22%28%29%2A%40%5B%5D%3F%26%2B%25%23%2C%3B%3D%3A__' .
'%2523%2525%2526%252B%252F%253F' .
'%C3%A9%C3%B8%C3%AF%D0%B2%CE%B2%E4%B8%AD%E5%9C%8B%E6%9B%B8%DB%9E';
// Public files should not be served by Drupal, so their URLs should not be
// routed through Drupal, whereas private files should be served by Drupal,
// so they need to be. The difference is most apparent when $script_path
// is not empty (i.e., when not using clean URLs).
$clean_url_settings = [
'clean' => '',
'unclean' => 'index.php/',
];
$public_directory_path = \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath();
foreach ($clean_url_settings as $clean_url_setting => $script_path) {
$clean_urls = $clean_url_setting == 'clean';
$request = $this->prepareRequestForGenerator($clean_urls);
$base_path = $request->getSchemeAndHttpHost() . $request->getBasePath();
$this->checkUrl('public', '', $basename, $base_path . '/' . $public_directory_path . '/' . $basename_encoded);
$this->checkUrl('private', '', $basename, $base_path . '/' . $script_path . 'system/files/' . $basename_encoded);
}
$this->assertEqual(file_create_url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', t('Generated URL matches expected URL.'));
}
/**
* Download a file from the URL generated by file_create_url().
*
* Create a file with the specified scheme, directory and filename; check that
* the URL generated by file_create_url() for the specified file equals the
* specified URL; fetch the URL and then compare the contents to the file.
*
* @param string $scheme
* A scheme, e.g. "public".
* @param string $directory
* A directory, possibly "".
* @param string $filename
* A filename.
* @param string $expected_url
* The expected URL.
*/
private function checkUrl($scheme, $directory, $filename, $expected_url) {
// Convert $filename to a valid filename, i.e. strip characters not
// supported by the filesystem, and create the file in the specified
// directory.
$filepath = file_create_filename($filename, $directory);
$directory_uri = $scheme . '://' . dirname($filepath);
file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY);
$file = $this->createFile($filepath, NULL, $scheme);
$url = file_create_url($file->getFileUri());
$this->assertEqual($url, $expected_url);
if ($scheme == 'private') {
// Tell the implementation of hook_file_download() in file_test.module
// that this file may be downloaded.
file_test_set_return('download', ['x-foo' => 'Bar']);
}
$this->drupalGet($url);
if ($this->assertResponse(200) == 'pass') {
$this->assertRaw(file_get_contents($file->getFileUri()), 'Contents of the file are correct.');
}
$file->delete();
}
}

View file

@ -0,0 +1,169 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\node\Entity\Node;
use Drupal\user\RoleInterface;
use Drupal\file\Entity\File;
/**
* Confirm that file field submissions work correctly for anonymous visitors.
*
* @group file
*/
class FileFieldAnonymousSubmissionTest extends FileFieldTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Set up permissions for anonymous attacker user.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [
'create article content' => TRUE,
'access content' => TRUE,
]);
}
/**
* Tests the basic node submission for an anonymous visitor.
*/
public function testAnonymousNode() {
$bundle_label = 'Article';
$node_title = 'test page';
// Load the node form.
$this->drupalLogout();
$this->drupalGet('node/add/article');
$this->assertResponse(200, 'Loaded the article node form.');
$this->assertText(strip_tags(t('Create @name', ['@name' => $bundle_label])));
$edit = [
'title[0][value]' => $node_title,
'body[0][value]' => 'Test article',
];
$this->drupalPostForm(NULL, $edit, 'Save');
$this->assertResponse(200);
$t_args = ['@type' => $bundle_label, '%title' => $node_title];
$this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
$matches = [];
if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
$nid = end($matches);
$this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.');
$node = Node::load($nid);
$this->assertNotEqual($node, NULL, 'The node was loaded successfully.');
}
}
/**
* Tests file submission for an anonymous visitor.
*/
public function testAnonymousNodeWithFile() {
$bundle_label = 'Article';
$node_title = 'Test page';
$this->createFileField('field_image', 'node', 'article', [], ['file_extensions' => 'txt png']);
// Load the node form.
$this->drupalLogout();
$this->drupalGet('node/add/article');
$this->assertResponse(200, 'Loaded the article node form.');
$this->assertText(strip_tags(t('Create @name', ['@name' => $bundle_label])));
// Generate an image file.
$image = $this->getTestFile('image');
// Submit the form.
$edit = [
'title[0][value]' => $node_title,
'body[0][value]' => 'Test article',
'files[field_image_0]' => $this->container->get('file_system')->realpath($image->getFileUri()),
];
$this->drupalPostForm(NULL, $edit, 'Save');
$this->assertResponse(200);
$t_args = ['@type' => $bundle_label, '%title' => $node_title];
$this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
$matches = [];
if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
$nid = end($matches);
$this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.');
$node = Node::load($nid);
$this->assertNotEqual($node, NULL, 'The node was loaded successfully.');
$this->assertFileExists(File::load($node->field_image->target_id), 'The image was uploaded successfully.');
}
}
/**
* Tests file submission for an anonymous visitor with a missing node title.
*/
public function testAnonymousNodeWithFileWithoutTitle() {
$this->drupalLogout();
$this->doTestNodeWithFileWithoutTitle();
}
/**
* Tests file submission for an authenticated user with a missing node title.
*/
public function testAuthenticatedNodeWithFileWithoutTitle() {
$admin_user = $this->drupalCreateUser([
'bypass node access',
'access content overview',
'administer nodes',
]);
$this->drupalLogin($admin_user);
$this->doTestNodeWithFileWithoutTitle();
}
/**
* Helper method to test file submissions with missing node titles.
*/
protected function doTestNodeWithFileWithoutTitle() {
$bundle_label = 'Article';
$node_title = 'Test page';
$this->createFileField('field_image', 'node', 'article', [], ['file_extensions' => 'txt png']);
// Load the node form.
$this->drupalGet('node/add/article');
$this->assertResponse(200, 'Loaded the article node form.');
$this->assertText(strip_tags(t('Create @name', ['@name' => $bundle_label])));
// Generate an image file.
$image = $this->getTestFile('image');
// Submit the form but exclude the title field.
$edit = [
'body[0][value]' => 'Test article',
'files[field_image_0]' => $this->container->get('file_system')->realpath($image->getFileUri()),
];
if (!$this->loggedInUser) {
$label = 'Save';
}
else {
$label = 'Save';
}
$this->drupalPostForm(NULL, $edit, $label);
$this->assertResponse(200);
$t_args = ['@type' => $bundle_label, '%title' => $node_title];
$this->assertNoText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
$this->assertText('Title field is required.');
// Submit the form again but this time with the missing title field. This
// should still work.
$edit = [
'title[0][value]' => $node_title,
];
$this->drupalPostForm(NULL, $edit, $label);
// Confirm the final submission actually worked.
$t_args = ['@type' => $bundle_label, '%title' => $node_title];
$this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.');
$matches = [];
if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
$nid = end($matches);
$this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.');
$node = Node::load($nid);
$this->assertNotEqual($node, NULL, 'The node was loaded successfully.');
$this->assertFileExists(File::load($node->field_image->target_id), 'The image was uploaded successfully.');
}
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
/**
* Provides methods for creating file fields.
*/
trait FileFieldCreationTrait {
/**
* Creates a new file field.
*
* @param string $name
* The name of the new field (all lowercase), exclude the "field_" prefix.
* @param string $entity_type
* The entity type.
* @param string $bundle
* The bundle that this field will be added to.
* @param array $storage_settings
* A list of field storage settings that will be added to the defaults.
* @param array $field_settings
* A list of instance settings that will be added to the instance defaults.
* @param array $widget_settings
* A list of widget settings that will be added to the widget defaults.
*
* @return \Drupal\field\FieldStorageConfigInterface
* The file field.
*/
public function createFileField($name, $entity_type, $bundle, $storage_settings = [], $field_settings = [], $widget_settings = []) {
$field_storage = FieldStorageConfig::create([
'entity_type' => $entity_type,
'field_name' => $name,
'type' => 'file',
'settings' => $storage_settings,
'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1,
]);
$field_storage->save();
$this->attachFileField($name, $entity_type, $bundle, $field_settings, $widget_settings);
return $field_storage;
}
/**
* Attaches a file field to an entity.
*
* @param string $name
* The name of the new field (all lowercase), exclude the "field_" prefix.
* @param string $entity_type
* The entity type this field will be added to.
* @param string $bundle
* The bundle this field will be added to.
* @param array $field_settings
* A list of field settings that will be added to the defaults.
* @param array $widget_settings
* A list of widget settings that will be added to the widget defaults.
*/
public function attachFileField($name, $entity_type, $bundle, $field_settings = [], $widget_settings = []) {
$field = [
'field_name' => $name,
'label' => $name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'required' => !empty($field_settings['required']),
'settings' => $field_settings,
];
FieldConfig::create($field)->save();
entity_get_form_display($entity_type, $bundle, 'default')
->setComponent($name, [
'type' => 'file_generic',
'settings' => $widget_settings,
])
->save();
// Assign display settings.
entity_get_display($entity_type, $bundle, 'default')
->setComponent($name, [
'label' => 'hidden',
'type' => 'file_default',
])
->save();
}
}

View file

@ -0,0 +1,223 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\file\Entity\File;
use Drupal\node\Entity\Node;
/**
* Tests the display of file fields in node and views.
*
* @group file
*/
class FileFieldDisplayTest extends FileFieldTestBase {
/**
* Tests normal formatter display on node display.
*/
public function testNodeDisplay() {
$field_name = strtolower($this->randomMachineName());
$type_name = 'article';
$field_storage_settings = [
'display_field' => '1',
'display_default' => '1',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$field_settings = [
'description_field' => '1',
];
$widget_settings = [];
$this->createFileField($field_name, 'node', $type_name, $field_storage_settings, $field_settings, $widget_settings);
// Create a new node *without* the file field set, and check that the field
// is not shown for each node display.
$node = $this->drupalCreateNode(['type' => $type_name]);
// Check file_default last as the assertions below assume that this is the
// case.
$file_formatters = ['file_table', 'file_url_plain', 'hidden', 'file_default'];
foreach ($file_formatters as $formatter) {
if ($formatter === 'hidden') {
$edit = [
"fields[$field_name][region]" => 'hidden',
];
}
else {
$edit = [
"fields[$field_name][type]" => $formatter,
"fields[$field_name][region]" => 'content',
];
}
$this->drupalPostForm("admin/structure/types/manage/$type_name/display", $edit, t('Save'));
$this->drupalGet('node/' . $node->id());
$this->assertNoText($field_name, format_string('Field label is hidden when no file attached for formatter %formatter', ['%formatter' => $formatter]));
}
$this->generateFile('escaped-&-text', 64, 10, 'text');
$test_file = File::create([
'uri' => 'public://escaped-&-text.txt',
'name' => 'escaped-&-text',
'filesize' => filesize('public://escaped-&-text.txt'),
]);
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
// Check that the default formatter is displaying with the file name.
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$file_link = [
'#theme' => 'file_link',
'#file' => $node_file,
];
$default_output = \Drupal::service('renderer')->renderRoot($file_link);
$this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.');
// Turn the "display" option off and check that the file is no longer displayed.
$edit = [$field_name . '[0][display]' => FALSE];
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
$this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.');
// Add a description and make sure that it is displayed.
$description = $this->randomMachineName();
$edit = [
$field_name . '[0][description]' => $description,
$field_name . '[0][display]' => TRUE,
];
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
$this->assertText($description);
// Ensure the filename in the link's title attribute is escaped.
$this->assertRaw('title="escaped-&amp;-text.txt"');
// Test that fields appear as expected after during the preview.
// Add a second file.
$name = 'files[' . $field_name . '_1][]';
$edit_upload[$name] = \Drupal::service('file_system')->realpath($test_file->getFileUri());
$this->drupalPostForm("node/$nid/edit", $edit_upload, t('Upload'));
// Uncheck the display checkboxes and go to the preview.
$edit[$field_name . '[0][display]'] = FALSE;
$edit[$field_name . '[1][display]'] = FALSE;
$this->drupalPostForm(NULL, $edit, t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->assertRaw($field_name . '[0][display]', 'First file appears as expected.');
$this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.');
$this->assertSession()->responseContains($field_name . '[1][description]', 'Description of second file appears as expected.');
}
/**
* Tests default display of File Field.
*/
public function testDefaultFileFieldDisplay() {
$field_name = strtolower($this->randomMachineName());
$type_name = 'article';
$field_storage_settings = [
'display_field' => '1',
'display_default' => '0',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$field_settings = [
'description_field' => '1',
];
$widget_settings = [];
$this->createFileField($field_name, 'node', $type_name, $field_storage_settings, $field_settings, $widget_settings);
$test_file = $this->getTestFile('text');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$this->drupalGet('node/' . $nid . '/edit');
$this->assertFieldByXPath('//input[@type="checkbox" and @name="' . $field_name . '[0][display]"]', NULL, 'Default file display checkbox field exists.');
$this->assertFieldByXPath('//input[@type="checkbox" and @name="' . $field_name . '[0][display]" and not(@checked)]', NULL, 'Default file display is off.');
}
/**
* Tests description toggle for field instance configuration.
*/
public function testDescToggle() {
$type_name = 'test';
$field_type = 'file';
$field_name = strtolower($this->randomMachineName());
// Use the UI to add a new content type that also contains a file field.
$edit = [
'name' => $type_name,
'type' => $type_name,
];
$this->drupalPostForm('admin/structure/types/add', $edit, t('Save and manage fields'));
$edit = [
'new_storage_type' => $field_type,
'field_name' => $field_name,
'label' => $this->randomString(),
];
$this->drupalPostForm('/admin/structure/types/manage/' . $type_name . '/fields/add-field', $edit, t('Save and continue'));
$this->drupalPostForm(NULL, [], t('Save field settings'));
// Ensure the description field is selected on the field instance settings
// form. That's what this test is all about.
$edit = [
'settings[description_field]' => TRUE,
];
$this->drupalPostForm(NULL, $edit, t('Save settings'));
// Add a node of our new type and upload a file to it.
$file = current($this->drupalGetTestFiles('text'));
$title = $this->randomString();
$edit = [
'title[0][value]' => $title,
'files[field_' . $field_name . '_0]' => \Drupal::service('file_system')->realpath($file->uri),
];
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($title);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertText(t('The description may be used as the label of the link to the file.'));
}
/**
* Tests description display of File Field.
*/
public function testDescriptionDefaultFileFieldDisplay() {
$field_name = strtolower($this->randomMachineName());
$type_name = 'article';
$field_storage_settings = [
'display_field' => '1',
'display_default' => '1',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$field_settings = [
'description_field' => '1',
];
$widget_settings = [];
$this->createFileField($field_name, 'node', $type_name, $field_storage_settings, $field_settings, $widget_settings);
$test_file = $this->getTestFile('text');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
// Add file description.
$description = 'This is the test file description';
$this->drupalPostForm("node/$nid/edit", [$field_name . '[0][description]' => $description], t('Save'));
// Load uncached node.
\Drupal::entityTypeManager()->getStorage('node')->resetCache([$nid]);
$node = Node::load($nid);
// Test default formatter.
$this->drupalGet('node/' . $nid);
$this->assertFieldByXPath('//a[@href="' . $node->{$field_name}->entity->url() . '"]', $description);
// Change formatter to "Table of files".
$display = \Drupal::entityTypeManager()->getStorage('entity_view_display')->load('node.' . $type_name . '.default');
$display->setComponent($field_name, [
'label' => 'hidden',
'type' => 'file_table',
])->save();
$this->drupalGet('node/' . $nid);
$this->assertFieldByXPath('//a[@href="' . $node->{$field_name}->entity->url() . '"]', $description);
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\Tests\file\Functional;
/**
* Tests file formatter access.
* @group file
*/
class FileFieldFormatterAccessTest extends FileFieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'file', 'field_ui', 'file_test'];
/**
* Tests the custom access handler is invoked.
*/
public function testFileAccessHandler() {
$type_name = 'article';
$field_name = strtolower($this->randomMachineName());
$this->createFileField($field_name, 'node', $type_name);
\Drupal::state()->set('file_test_alternate_access_handler', TRUE);
\Drupal::entityManager()->clearCachedDefinitions();
$test_file = $this->getTestFile('text');
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$this->drupalGet('node/' . $nid);
$this->assertTrue(\Drupal::state()->get('file_access_formatter_check', FALSE));
}
}

View file

@ -0,0 +1,94 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
/**
* Tests that files are uploaded to proper locations.
*
* @group file
*/
class FileFieldPathTest extends FileFieldTestBase {
/**
* Tests the normal formatter display on node display.
*/
public function testUploadPath() {
/** @var \Drupal\node\NodeStorageInterface $node_storage */
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$field_name = strtolower($this->randomMachineName());
$type_name = 'article';
$this->createFileField($field_name, 'node', $type_name);
/** @var \Drupal\file\FileInterface $test_file */
$test_file = $this->getTestFile('text');
// Create a new node.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
// Check that the file was uploaded to the correct location.
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
/** @var \Drupal\file\FileInterface $node_file */
$node_file = $node->{$field_name}->entity;
$date_formatter = $this->container->get('date.formatter');
$expected_filename =
'public://' .
$date_formatter->format(REQUEST_TIME, 'custom', 'Y') . '-' .
$date_formatter->format(REQUEST_TIME, 'custom', 'm') . '/' .
$test_file->getFilename();
$this->assertPathMatch($expected_filename, $node_file->getFileUri(), format_string('The file %file was uploaded to the correct path.', ['%file' => $node_file->getFileUri()]));
// Change the path to contain multiple subdirectories.
$this->updateFileField($field_name, $type_name, ['file_directory' => 'foo/bar/baz']);
// Upload a new file into the subdirectories.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
// Check that the file was uploaded into the subdirectory.
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertPathMatch('public://foo/bar/baz/' . $test_file->getFilename(), $node_file->getFileUri(), format_string('The file %file was uploaded to the correct path.', ['%file' => $node_file->getFileUri()]));
// Check the path when used with tokens.
// Change the path to contain multiple token directories.
$this->updateFileField($field_name, $type_name, ['file_directory' => '[current-user:uid]/[current-user:name]']);
// Upload a new file into the token subdirectories.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
// Check that the file was uploaded into the subdirectory.
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
// Do token replacement using the same user which uploaded the file, not
// the user running the test case.
$data = ['user' => $this->adminUser];
$subdirectory = \Drupal::token()->replace('[user:uid]/[user:name]', $data);
$this->assertPathMatch('public://' . $subdirectory . '/' . $test_file->getFilename(), $node_file->getFileUri(), format_string('The file %file was uploaded to the correct path with token replacements.', ['%file' => $node_file->getFileUri()]));
}
/**
* Asserts that a file is uploaded to the right location.
*
* @param string $expected_path
* The location where the file is expected to be uploaded. Duplicate file
* names to not need to be taken into account.
* @param string $actual_path
* Where the file was actually uploaded.
* @param string $message
* The message to display with this assertion.
*/
public function assertPathMatch($expected_path, $actual_path, $message) {
// Strip off the extension of the expected path to allow for _0, _1, etc.
// suffixes when the file hits a duplicate name.
$pos = strrpos($expected_path, '.');
$base_path = substr($expected_path, 0, $pos);
$extension = substr($expected_path, $pos + 1);
$result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path);
$this->assertTrue($result, $message);
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
/**
* Ensure that files added to nodes appear correctly in RSS feeds.
*
* @group file
*/
class FileFieldRSSContentTest extends FileFieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'views'];
/**
* Tests RSS enclosure formatter display for RSS feeds.
*/
public function testFileFieldRSSContent() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$field_name = strtolower($this->randomMachineName());
$type_name = 'article';
$this->createFileField($field_name, 'node', $type_name);
// RSS display must be added manually.
$this->drupalGet("admin/structure/types/manage/$type_name/display");
$edit = [
"display_modes_custom[rss]" => '1',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
// Change the format to 'RSS enclosure'.
$this->drupalGet("admin/structure/types/manage/$type_name/display/rss");
$edit = [
"fields[$field_name][type]" => 'file_rss_enclosure',
"fields[$field_name][region]" => 'content',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
// Create a new node with a file field set. Promote to frontpage
// needs to be set so this node will appear in the RSS feed.
$node = $this->drupalCreateNode(['type' => $type_name, 'promote' => 1]);
$test_file = $this->getTestFile('text');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $node->id());
// Get the uploaded file from the node.
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
// Check that the RSS enclosure appears in the RSS feed.
$this->drupalGet('rss.xml');
$uploaded_filename = str_replace('public://', '', $node_file->getFileUri());
$selector = sprintf(
'enclosure[@url="%s"][@length="%s"][@type="%s"]',
file_create_url("public://$uploaded_filename", ['absolute' => TRUE]),
$node_file->getSize(),
$node_file->getMimeType()
);
$this->assertNotNull($this->getSession()->getDriver()->find('xpath', $selector), 'File field RSS enclosure is displayed when viewing the RSS feed.');
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
/**
* Tests creating and deleting revisions with files attached.
*
* @group file
*/
class FileFieldRevisionTest extends FileFieldTestBase {
/**
* Tests creating multiple revisions of a node and managing attached files.
*
* Expected behaviors:
* - Adding a new revision will make another entry in the field table, but
* the original file will not be duplicated.
* - Deleting a revision should not delete the original file if the file
* is in use by another revision.
* - When the last revision that uses a file is deleted, the original file
* should be deleted also.
*/
public function testRevisions() {
// This test expects unused managed files to be marked as a temporary file
// and then deleted up by file_cron().
$this->config('file.settings')
->set('make_unused_managed_files_temporary', TRUE)
->save();
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$type_name = 'article';
$field_name = strtolower($this->randomMachineName());
$this->createFileField($field_name, 'node', $type_name);
// Create the same fields for users.
$this->createFileField($field_name, 'user', 'user');
$test_file = $this->getTestFile('text');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
// Check that the file exists on disk and in the database.
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file_r1 = File::load($node->{$field_name}->target_id);
$node_vid_r1 = $node->getRevisionId();
$this->assertFileExists($node_file_r1, 'New file saved to disk on node creation.');
$this->assertFileEntryExists($node_file_r1, 'File entry exists in database on node creation.');
$this->assertFileIsPermanent($node_file_r1, 'File is permanent.');
// Upload another file to the same node in a new revision.
$this->replaceNodeFile($test_file, $field_name, $nid);
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file_r2 = File::load($node->{$field_name}->target_id);
$node_vid_r2 = $node->getRevisionId();
$this->assertFileExists($node_file_r2, 'Replacement file exists on disk after creating new revision.');
$this->assertFileEntryExists($node_file_r2, 'Replacement file entry exists in database after creating new revision.');
$this->assertFileIsPermanent($node_file_r2, 'Replacement file is permanent.');
// Check that the original file is still in place on the first revision.
$node = node_revision_load($node_vid_r1);
$current_file = File::load($node->{$field_name}->target_id);
$this->assertEqual($node_file_r1->id(), $current_file->id(), 'Original file still in place after replacing file in new revision.');
$this->assertFileExists($node_file_r1, 'Original file still in place after replacing file in new revision.');
$this->assertFileEntryExists($node_file_r1, 'Original file entry still in place after replacing file in new revision');
$this->assertFileIsPermanent($node_file_r1, 'Original file is still permanent.');
// Save a new version of the node without any changes.
// Check that the file is still the same as the previous revision.
$this->drupalPostForm('node/' . $nid . '/edit', ['revision' => '1'], t('Save'));
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file_r3 = File::load($node->{$field_name}->target_id);
$node_vid_r3 = $node->getRevisionId();
$this->assertEqual($node_file_r2->id(), $node_file_r3->id(), 'Previous revision file still in place after creating a new revision without a new file.');
$this->assertFileIsPermanent($node_file_r3, 'New revision file is permanent.');
// Revert to the first revision and check that the original file is active.
$this->drupalPostForm('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', [], t('Revert'));
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file_r4 = File::load($node->{$field_name}->target_id);
$this->assertEqual($node_file_r1->id(), $node_file_r4->id(), 'Original revision file still in place after reverting to the original revision.');
$this->assertFileIsPermanent($node_file_r4, 'Original revision file still permanent after reverting to the original revision.');
// Delete the second revision and check that the file is kept (since it is
// still being used by the third revision).
$this->drupalPostForm('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', [], t('Delete'));
$this->assertFileExists($node_file_r3, 'Second file is still available after deleting second revision, since it is being used by the third revision.');
$this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting second revision, since it is being used by the third revision.');
$this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting second revision, since it is being used by the third revision.');
// Attach the second file to a user.
$user = $this->drupalCreateUser();
$user->$field_name->target_id = $node_file_r3->id();
$user->$field_name->display = 1;
$user->save();
$this->drupalGet('user/' . $user->id() . '/edit');
// Delete the third revision and check that the file is not deleted yet.
$this->drupalPostForm('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', [], t('Delete'));
$this->assertFileExists($node_file_r3, 'Second file is still available after deleting third revision, since it is being used by the user.');
$this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting third revision, since it is being used by the user.');
$this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting third revision, since it is being used by the user.');
// Delete the user and check that the file is also deleted.
$user->delete();
// TODO: This seems like a bug in File API. Clearing the stat cache should
// not be necessary here. The file really is deleted, but stream wrappers
// doesn't seem to think so unless we clear the PHP file stat() cache.
clearstatcache($node_file_r1->getFileUri());
clearstatcache($node_file_r2->getFileUri());
clearstatcache($node_file_r3->getFileUri());
clearstatcache($node_file_r4->getFileUri());
// Call file_cron() to clean up the file. Make sure the changed timestamp
// of the file is older than the system.file.temporary_maximum_age
// configuration value.
db_update('file_managed')
->fields([
'changed' => REQUEST_TIME - ($this->config('system.file')->get('temporary_maximum_age') + 1),
])
->condition('fid', $node_file_r3->id())
->execute();
\Drupal::service('cron')->run();
$this->assertFileNotExists($node_file_r3, 'Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.');
$this->assertFileEntryNotExists($node_file_r3, 'Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.');
// Delete the entire node and check that the original file is deleted.
$this->drupalPostForm('node/' . $nid . '/delete', [], t('Delete'));
// Call file_cron() to clean up the file. Make sure the changed timestamp
// of the file is older than the system.file.temporary_maximum_age
// configuration value.
db_update('file_managed')
->fields([
'changed' => REQUEST_TIME - ($this->config('system.file')->get('temporary_maximum_age') + 1),
])
->condition('fid', $node_file_r1->id())
->execute();
\Drupal::service('cron')->run();
$this->assertFileNotExists($node_file_r1, 'Original file is deleted after deleting the entire node with two revisions remaining.');
$this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.');
}
}

View file

@ -0,0 +1,256 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\FileInterface;
use Drupal\Tests\BrowserTestBase;
use Drupal\file\Entity\File;
use Drupal\Tests\TestFileCreationTrait;
/**
* Provides methods specifically for testing File module's field handling.
*/
abstract class FileFieldTestBase extends BrowserTestBase {
use FileFieldCreationTrait;
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'file', 'file_module_test', 'field_ui'];
/**
* An user with administration permissions.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer node fields', 'administer node display', 'administer nodes', 'bypass node access']);
$this->drupalLogin($this->adminUser);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
}
/**
* Retrieves a sample file of the specified type.
*
* @return \Drupal\file\FileInterface
*/
public function getTestFile($type_name, $size = NULL) {
// Get a file to upload.
$file = current($this->drupalGetTestFiles($type_name, $size));
// Add a filesize property to files as would be read by
// \Drupal\file\Entity\File::load().
$file->filesize = filesize($file->uri);
return File::create((array) $file);
}
/**
* Retrieves the fid of the last inserted file.
*/
public function getLastFileId() {
return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
}
/**
* Updates an existing file field with new settings.
*/
public function updateFileField($name, $type_name, $field_settings = [], $widget_settings = []) {
$field = FieldConfig::loadByName('node', $type_name, $name);
$field->setSettings(array_merge($field->getSettings(), $field_settings));
$field->save();
entity_get_form_display('node', $type_name, 'default')
->setComponent($name, [
'settings' => $widget_settings,
])
->save();
}
/**
* Uploads a file to a node.
*
* @param \Drupal\file\FileInterface $file
* The File to be uploaded.
* @param string $field_name
* The name of the field on which the files should be saved.
* @param $nid_or_type
* A numeric node id to upload files to an existing node, or a string
* indicating the desired bundle for a new node.
* @param bool $new_revision
* The revision number.
* @param array $extras
* Additional values when a new node is created.
*
* @return int
* The node id.
*/
public function uploadNodeFile(FileInterface $file, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = []) {
return $this->uploadNodeFiles([$file], $field_name, $nid_or_type, $new_revision, $extras);
}
/**
* Uploads multiple files to a node.
*
* @param \Drupal\file\FileInterface[] $files
* The files to be uploaded.
* @param string $field_name
* The name of the field on which the files should be saved.
* @param $nid_or_type
* A numeric node id to upload files to an existing node, or a string
* indicating the desired bundle for a new node.
* @param bool $new_revision
* The revision number.
* @param array $extras
* Additional values when a new node is created.
*
* @return int
* The node id.
*/
public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = []) {
$edit = [
'title[0][value]' => $this->randomMachineName(),
'revision' => (string) (int) $new_revision,
];
$node_storage = $this->container->get('entity.manager')->getStorage('node');
if (is_numeric($nid_or_type)) {
$nid = $nid_or_type;
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
}
else {
// Add a new node.
$extras['type'] = $nid_or_type;
$node = $this->drupalCreateNode($extras);
$nid = $node->id();
// Save at least one revision to better simulate a real site.
$node->setNewRevision();
$node->save();
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$this->assertNotEqual($nid, $node->getRevisionId(), 'Node revision exists.');
}
// Attach files to the node.
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
// File input name depends on number of files already uploaded.
$field_num = count($node->{$field_name});
$name = 'files[' . $field_name . "_$field_num]";
if ($field_storage->getCardinality() != 1) {
$name .= '[]';
}
foreach ($files as $file) {
$file_path = $this->container->get('file_system')->realpath($file->getFileUri());
if (count($files) == 1) {
$edit[$name] = $file_path;
}
else {
$edit[$name][] = $file_path;
}
}
$this->drupalPostForm("node/$nid/edit", $edit, t('Save'));
return $nid;
}
/**
* Removes a file from a node.
*
* Note that if replacing a file, it must first be removed then added again.
*/
public function removeNodeFile($nid, $new_revision = TRUE) {
$edit = [
'revision' => (string) (int) $new_revision,
];
$this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
$this->drupalPostForm(NULL, $edit, t('Save'));
}
/**
* Replaces a file within a node.
*/
public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
$edit = [
'files[' . $field_name . '_0]' => \Drupal::service('file_system')->realpath($file->getFileUri()),
'revision' => (string) (int) $new_revision,
];
$this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
$this->drupalPostForm(NULL, $edit, t('Save'));
}
/**
* Asserts that a file exists physically on disk.
*
* Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with
* file entities.
*
* @param \Drupal\File\FileInterface|string $file
* Either the file entity or the file URI.
* @param string $message
* (optional) A message to display with the assertion.
*/
public static function assertFileExists($file, $message = NULL) {
$message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]);
$filename = $file instanceof FileInterface ? $file->getFileUri() : $file;
parent::assertFileExists($filename, $message);
}
/**
* Asserts that a file exists in the database.
*/
public function assertFileEntryExists($file, $message = NULL) {
$this->container->get('entity.manager')->getStorage('file')->resetCache();
$db_file = File::load($file->id());
$message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]);
$this->assertEqual($db_file->getFileUri(), $file->getFileUri(), $message);
}
/**
* Asserts that a file does not exist on disk.
*
* Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with
* file entities.
*
* @param \Drupal\File\FileInterface|string $file
* Either the file entity or the file URI.
* @param string $message
* (optional) A message to display with the assertion.
*/
public static function assertFileNotExists($file, $message = NULL) {
$message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]);
$filename = $file instanceof FileInterface ? $file->getFileUri() : $file;
parent::assertFileNotExists($filename, $message);
}
/**
* Asserts that a file does not exist in the database.
*/
public function assertFileEntryNotExists($file, $message) {
$this->container->get('entity.manager')->getStorage('file')->resetCache();
$message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]);
$this->assertFalse(File::load($file->id()), $message);
}
/**
* Asserts that a file's status is set to permanent in the database.
*/
public function assertFileIsPermanent(FileInterface $file, $message = NULL) {
$message = isset($message) ? $message : format_string('File %file is permanent.', ['%file' => $file->getFileUri()]);
$this->assertTrue($file->isPermanent(), $message);
}
}

View file

@ -0,0 +1,190 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
/**
* Tests validation functions such as file type, max file size, max size per
* node, and required.
*
* @group file
*/
class FileFieldValidateTest extends FileFieldTestBase {
/**
* Tests the required property on file fields.
*/
public function testRequired() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$type_name = 'article';
$field_name = strtolower($this->randomMachineName());
$storage = $this->createFileField($field_name, 'node', $type_name, [], ['required' => '1']);
$field = FieldConfig::loadByName('node', $type_name, $field_name);
$test_file = $this->getTestFile('text');
// Try to post a new node without uploading a file.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
$this->assertRaw(t('@title field is required.', ['@title' => $field->getLabel()]), 'Node save failed when required file field was empty.');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$this->assertTrue($nid !== FALSE, format_string('uploadNodeFile(@test_file, @field_name, @type_name) succeeded', ['@test_file' => $test_file->getFileUri(), '@field_name' => $field_name, '@type_name' => $type_name]));
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertFileExists($node_file, 'File exists after uploading to the required field.');
$this->assertFileEntryExists($node_file, 'File entry exists after uploading to the required field.');
// Try again with a multiple value field.
$storage->delete();
$this->createFileField($field_name, 'node', $type_name, ['cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED], ['required' => '1']);
// Try to post a new node without uploading a file in the multivalue field.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
$this->assertRaw(t('@title field is required.', ['@title' => $field->getLabel()]), 'Node save failed when required multiple value file field was empty.');
// Create a new node with the uploaded file into the multivalue field.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertFileExists($node_file, 'File exists after uploading to the required multiple value field.');
$this->assertFileEntryExists($node_file, 'File entry exists after uploading to the required multiple value field.');
}
/**
* Tests the max file size validator.
*/
public function testFileMaxSize() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$type_name = 'article';
$field_name = strtolower($this->randomMachineName());
$this->createFileField($field_name, 'node', $type_name, [], ['required' => '1']);
// 128KB.
$small_file = $this->getTestFile('text', 131072);
// 1.2MB
$large_file = $this->getTestFile('text', 1310720);
// Test uploading both a large and small file with different increments.
$sizes = [
'1M' => 1048576,
'1024K' => 1048576,
'1048576' => 1048576,
];
foreach ($sizes as $max_filesize => $file_limit) {
// Set the max file upload size.
$this->updateFileField($field_name, $type_name, ['max_filesize' => $max_filesize]);
// Create a new node with the small file, which should pass.
$nid = $this->uploadNodeFile($small_file, $field_name, $type_name);
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertFileExists($node_file, format_string('File exists after uploading a file (%filesize) under the max limit (%maxsize).', ['%filesize' => format_size($small_file->getSize()), '%maxsize' => $max_filesize]));
$this->assertFileEntryExists($node_file, format_string('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', ['%filesize' => format_size($small_file->getSize()), '%maxsize' => $max_filesize]));
// Check that uploading the large file fails (1M limit).
$this->uploadNodeFile($large_file, $field_name, $type_name);
$error_message = t('The file is %filesize exceeding the maximum file size of %maxsize.', ['%filesize' => format_size($large_file->getSize()), '%maxsize' => format_size($file_limit)]);
$this->assertRaw($error_message, format_string('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', ['%filesize' => format_size($large_file->getSize()), '%maxsize' => $max_filesize]));
}
// Turn off the max filesize.
$this->updateFileField($field_name, $type_name, ['max_filesize' => '']);
// Upload the big file successfully.
$nid = $this->uploadNodeFile($large_file, $field_name, $type_name);
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertFileExists($node_file, format_string('File exists after uploading a file (%filesize) with no max limit.', ['%filesize' => format_size($large_file->getSize())]));
$this->assertFileEntryExists($node_file, format_string('File entry exists after uploading a file (%filesize) with no max limit.', ['%filesize' => format_size($large_file->getSize())]));
}
/**
* Tests file extension checking.
*/
public function testFileExtension() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$type_name = 'article';
$field_name = strtolower($this->randomMachineName());
$this->createFileField($field_name, 'node', $type_name);
$test_file = $this->getTestFile('image');
list(, $test_file_extension) = explode('.', $test_file->getFilename());
// Disable extension checking.
$this->updateFileField($field_name, $type_name, ['file_extensions' => '']);
// Check that the file can be uploaded with no extension checking.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertFileExists($node_file, 'File exists after uploading a file with no extension checking.');
$this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with no extension checking.');
// Enable extension checking for text files.
$this->updateFileField($field_name, $type_name, ['file_extensions' => 'txt']);
// Check that the file with the wrong extension cannot be uploaded.
$this->uploadNodeFile($test_file, $field_name, $type_name);
$error_message = t('Only files with the following extensions are allowed: %files-allowed.', ['%files-allowed' => 'txt']);
$this->assertRaw($error_message, 'Node save failed when file uploaded with the wrong extension.');
// Enable extension checking for text and image files.
$this->updateFileField($field_name, $type_name, ['file_extensions' => "txt $test_file_extension"]);
// Check that the file can be uploaded with extension checking.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertFileExists($node_file, 'File exists after uploading a file with extension checking.');
$this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with extension checking.');
}
/**
* Checks that a file can always be removed if it does not pass validation.
*/
public function testFileRemoval() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$type_name = 'article';
$field_name = 'file_test';
$this->createFileField($field_name, 'node', $type_name);
$test_file = $this->getTestFile('image');
// Disable extension checking.
$this->updateFileField($field_name, $type_name, ['file_extensions' => '']);
// Check that the file can be uploaded with no extension checking.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
$this->assertFileExists($node_file, 'File exists after uploading a file with no extension checking.');
$this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with no extension checking.');
// Enable extension checking for text files.
$this->updateFileField($field_name, $type_name, ['file_extensions' => 'txt']);
// Check that the file can still be removed.
$this->removeNodeFile($nid);
$this->assertNoText('Only files with the following extensions are allowed: txt.');
$this->assertText('Article ' . $node->getTitle() . ' has been updated.');
}
}

View file

@ -0,0 +1,227 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\node\Entity\Node;
use Drupal\file\Entity\File;
use Drupal\entity_test\Entity\EntityTestConstraints;
/**
* Tests file listing page functionality.
*
* @group file
*/
class FileListingTest extends FileFieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['views', 'file', 'image', 'entity_test'];
/**
* An authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser;
protected function setUp() {
parent::setUp();
// This test expects unused managed files to be marked as a temporary file.
$this->config('file.settings')
->set('make_unused_managed_files_temporary', TRUE)
->save();
$this->adminUser = $this->drupalCreateUser(['access files overview', 'bypass node access']);
$this->baseUser = $this->drupalCreateUser();
$this->createFileField('file', 'node', 'article', [], ['file_extensions' => 'txt png']);
}
/**
* Calculates total count of usages for a file.
*
* @param $usage array
* Array of file usage information as returned from file_usage subsystem.
* @return int
* Total usage count.
*/
protected function sumUsages($usage) {
$count = 0;
foreach ($usage as $module) {
foreach ($module as $entity_type) {
foreach ($entity_type as $entity) {
$count += $entity;
}
}
}
return $count;
}
/**
* Tests file overview with different user permissions.
*/
public function testFileListingPages() {
$file_usage = $this->container->get('file.usage');
// Users without sufficient permissions should not see file listing.
$this->drupalLogin($this->baseUser);
$this->drupalGet('admin/content/files');
$this->assertResponse(403);
// Log in with user with right permissions and test listing.
$this->drupalLogin($this->adminUser);
for ($i = 0; $i < 5; $i++) {
$nodes[] = $this->drupalCreateNode(['type' => 'article']);
}
$this->drupalGet('admin/content/files');
$this->assertResponse(200);
$this->assertText('No files available.');
$this->drupalGet('admin/content/files');
$this->assertResponse(200);
// Create a file with no usage.
$file = $this->createFile();
$this->drupalGet('admin/content/files/usage/' . $file->id());
$this->assertResponse(200);
$this->assertTitle(t('File usage information for @file | Drupal', ['@file' => $file->getFilename()]));
foreach ($nodes as &$node) {
$this->drupalGet('node/' . $node->id() . '/edit');
$file = $this->getTestFile('image');
$edit = [
'files[file_0]' => \Drupal::service('file_system')->realpath($file->getFileUri()),
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$node = Node::load($node->id());
}
$this->drupalGet('admin/content/files');
foreach ($nodes as $node) {
$file = File::load($node->file->target_id);
$this->assertText($file->getFilename());
$this->assertLinkByHref(file_create_url($file->getFileUri()));
$this->assertLinkByHref('admin/content/files/usage/' . $file->id());
}
$this->assertFalse(preg_match('/views-field-status priority-low\">\s*' . t('Temporary') . '/', $this->getSession()->getPage()->getContent()), 'All files are stored as permanent.');
// Use one file two times and check usage information.
$orphaned_file = $nodes[1]->file->target_id;
$used_file = $nodes[0]->file->target_id;
$nodes[1]->file->target_id = $used_file;
$nodes[1]->save();
$this->drupalGet('admin/content/files');
$file = File::load($orphaned_file);
$usage = $this->sumUsages($file_usage->listUsage($file));
$this->assertRaw('admin/content/files/usage/' . $file->id() . '">' . $usage);
$file = File::load($used_file);
$usage = $this->sumUsages($file_usage->listUsage($file));
$this->assertRaw('admin/content/files/usage/' . $file->id() . '">' . $usage);
$result = $this->xpath("//td[contains(@class, 'views-field-status') and contains(text(), :value)]", [':value' => 'Temporary']);
$this->assertEqual(1, count($result), 'Unused file marked as temporary.');
// Test file usage page.
foreach ($nodes as $node) {
$file = File::load($node->file->target_id);
$usage = $file_usage->listUsage($file);
$this->drupalGet('admin/content/files/usage/' . $file->id());
$this->assertResponse(200);
$this->assertText($node->getTitle(), 'Node title found on usage page.');
$this->assertText('node', 'Registering entity type found on usage page.');
$this->assertText('file', 'Registering module found on usage page.');
foreach ($usage as $module) {
foreach ($module as $entity_type) {
foreach ($entity_type as $entity) {
$this->assertText($entity, 'Usage count found on usage page.');
}
}
}
$this->assertLinkByHref('node/' . $node->id(), 0, 'Link to registering entity found on usage page.');
}
}
/**
* Tests file listing usage page for entities with no canonical link template.
*/
public function testFileListingUsageNoLink() {
// Login with user with right permissions and test listing.
$this->drupalLogin($this->adminUser);
// Create a bundle and attach a File field to the bundle.
$bundle = $this->randomMachineName();
entity_test_create_bundle($bundle, NULL, 'entity_test_constraints');
$this->createFileField('field_test_file', 'entity_test_constraints', $bundle, [], ['file_extensions' => 'txt png']);
// Create file to attach to entity.
$file = File::create([
'filename' => 'druplicon.txt',
'uri' => 'public://druplicon.txt',
'filemime' => 'text/plain',
]);
$file->setPermanent();
file_put_contents($file->getFileUri(), 'hello world');
$file->save();
// Create entity and attach the created file.
$entity_name = $this->randomMachineName();
$entity = EntityTestConstraints::create([
'uid' => 1,
'name' => $entity_name,
'type' => $bundle,
'field_test_file' => [
'target_id' => $file->id(),
],
]);
$entity->save();
// Create node entity and attach the created file.
$node = $this->drupalCreateNode(['type' => 'article', 'file' => $file]);
$node->save();
// Load the file usage page for the created and attached file.
$this->drupalGet('admin/content/files/usage/' . $file->id());
$this->assertResponse(200);
// Entity name should be displayed, but not linked if Entity::toUrl
// throws an exception
$this->assertText($entity_name, 'Entity name is added to file usage listing.');
$this->assertNoLink($entity_name, 'Linked entity name not added to file usage listing.');
$this->assertLink($node->getTitle());
}
/**
* Creates and saves a test file.
*
* @return \Drupal\Core\Entity\EntityInterface
* A file entity.
*/
protected function createFile() {
// Create a new file entity.
$file = File::create([
'uid' => 1,
'filename' => 'druplicon.txt',
'uri' => 'public://druplicon.txt',
'filemime' => 'text/plain',
'created' => 1,
'changed' => 1,
'status' => FILE_STATUS_PERMANENT,
]);
file_put_contents($file->getFileUri(), 'hello world');
// Save it, inserting a new record.
$file->save();
return $file;
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
use Drupal\user\Entity\Role;
/**
* Tests access to managed files.
*
* @group file
*/
class FileManagedAccessTest extends FileManagedTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Give anonymous users permission to access content, so they can view and
// download public files.
$anonymous_role = Role::load(Role::ANONYMOUS_ID);
$anonymous_role->grantPermission('access content');
$anonymous_role->save();
}
/**
* Tests if public file is always accessible.
*/
public function testFileAccess() {
// Create a new file entity.
$file = File::create([
'uid' => 1,
'filename' => 'drupal.txt',
'uri' => 'public://drupal.txt',
'filemime' => 'text/plain',
'status' => FILE_STATUS_PERMANENT,
]);
file_put_contents($file->getFileUri(), 'hello world');
// Save it, inserting a new record.
$file->save();
// Create authenticated user to check file access.
$account = $this->createUser(['access site reports', 'access content']);
$this->assertTrue($file->access('view', $account), 'Public file is viewable to authenticated user');
$this->assertTrue($file->access('download', $account), 'Public file is downloadable to authenticated user');
// Create anonymous user to check file access.
$account = $this->createUser()->getAnonymousUser();
$this->assertTrue($file->access('view', $account), 'Public file is viewable to anonymous user');
$this->assertTrue($file->access('download', $account), 'Public file is downloadable to anonymous user');
// Create a new file entity.
$file = File::create([
'uid' => 1,
'filename' => 'drupal.txt',
'uri' => 'private://drupal.txt',
'filemime' => 'text/plain',
'status' => FILE_STATUS_PERMANENT,
]);
file_put_contents($file->getFileUri(), 'hello world');
// Save it, inserting a new record.
$file->save();
// Create authenticated user to check file access.
$account = $this->createUser(['access site reports', 'access content']);
$this->assertFalse($file->access('view', $account), 'Private file is not viewable to authenticated user');
$this->assertFalse($file->access('download', $account), 'Private file is not downloadable to authenticated user');
// Create anonymous user to check file access.
$account = $this->createUser()->getAnonymousUser();
$this->assertFalse($file->access('view', $account), 'Private file is not viewable to anonymous user');
$this->assertFalse($file->access('download', $account), 'Private file is not downloadable to anonymous user');
}
}

View file

@ -0,0 +1,228 @@
<?php
namespace Drupal\Tests\file\Functional;
/**
* Tests the 'managed_file' element type.
*
* @group file
* @todo Create a FileTestBase class and move FileFieldTestBase methods
* that aren't related to fields into it.
*/
class FileManagedFileElementTest extends FileFieldTestBase {
/**
* Tests the managed_file element type.
*/
public function testManagedFile() {
// Check that $element['#size'] is passed to the child upload element.
$this->drupalGet('file/test');
$this->assertFieldByXpath('//input[@name="files[nested_file]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.');
// Perform the tests with all permutations of $form['#tree'],
// $element['#extended'], and $element['#multiple'].
$test_file = $this->getTestFile('text');
foreach ([0, 1] as $tree) {
foreach ([0, 1] as $extended) {
foreach ([0, 1] as $multiple) {
$path = 'file/test/' . $tree . '/' . $extended . '/' . $multiple;
$input_base_name = $tree ? 'nested_file' : 'file';
$file_field_name = $multiple ? 'files[' . $input_base_name . '][]' : 'files[' . $input_base_name . ']';
// Submit without a file.
$this->drupalPostForm($path, [], t('Save'));
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', [])]), 'Submitted without a file.');
// Submit with a file, but with an invalid form token. Ensure the file
// was not saved.
$last_fid_prior = $this->getLastFileId();
$this->drupalGet($path);
$form_token_field = $this->assertSession()->hiddenFieldExists('form_token');
$form_token_field->setValue('invalid token');
$edit = [
$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri()),
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
$last_fid = $this->getLastFileId();
$this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
// Submit a new file, without using the Upload button.
$last_fid_prior = $this->getLastFileId();
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
$this->drupalPostForm($path, $edit, t('Save'));
$last_fid = $this->getLastFileId();
$this->assertTrue($last_fid > $last_fid_prior, 'New file got saved.');
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', [$last_fid])]), 'Submit handler has correct file info.');
// Submit no new input, but with a default file.
$this->drupalPostForm($path . '/' . $last_fid, [], t('Save'));
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', [$last_fid])]), 'Empty submission did not change an existing file.');
// Upload, then Submit.
$last_fid_prior = $this->getLastFileId();
$this->drupalGet($path);
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
$this->drupalPostForm(NULL, $edit, t('Upload'));
$last_fid = $this->getLastFileId();
$this->assertTrue($last_fid > $last_fid_prior, 'New file got uploaded.');
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', [$last_fid])]), 'Submit handler has correct file info.');
// Remove, then Submit.
$remove_button_title = $multiple ? t('Remove selected') : t('Remove');
$remove_edit = [];
if ($multiple) {
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $last_fid . '][selected]';
$remove_edit = [$selected_checkbox => '1'];
}
$this->drupalGet($path . '/' . $last_fid);
$this->drupalPostForm(NULL, $remove_edit, $remove_button_title);
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertRaw(t('The file ids are %fids.', ['%fids' => '']), 'Submission after file removal was successful.');
// Upload, then Remove, then Submit.
$this->drupalGet($path);
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
$this->drupalPostForm(NULL, $edit, t('Upload'));
$remove_edit = [];
if ($multiple) {
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]';
$remove_edit = [$selected_checkbox => '1'];
}
$this->drupalPostForm(NULL, $remove_edit, $remove_button_title);
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertRaw(t('The file ids are %fids.', ['%fids' => '']), 'Submission after file upload and removal was successful.');
}
}
}
// The multiple file upload has additional conditions that need checking.
$path = 'file/test/1/1/1';
$edit = ['files[nested_file][]' => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
$fid_list = [];
$this->drupalGet($path);
// Add a single file to the upload field.
$this->drupalPostForm(NULL, $edit, t('Upload'));
$fid_list[] = $this->getLastFileId();
$this->assertFieldByXpath('//input[@name="nested[file][file_' . $fid_list[0] . '][selected]"]', NULL, 'First file successfully uploaded to multiple file element.');
// Add another file to the same upload field.
$this->drupalPostForm(NULL, $edit, t('Upload'));
$fid_list[] = $this->getLastFileId();
$this->assertFieldByXpath('//input[@name="nested[file][file_' . $fid_list[1] . '][selected]"]', NULL, 'Second file successfully uploaded to multiple file element.');
// Save the entire form.
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', $fid_list)]), 'Two files saved into a single multiple file element.');
// Delete only the first file.
$edit = [
'nested[file][file_' . $fid_list[0] . '][selected]' => '1',
];
$this->drupalPostForm($path . '/' . implode(',', $fid_list), $edit, t('Remove selected'));
// Check that the first file has been deleted but not the second.
$this->assertNoFieldByXpath('//input[@name="nested[file][file_' . $fid_list[0] . '][selected]"]', NULL, 'An individual file can be deleted from a multiple file element.');
$this->assertFieldByXpath('//input[@name="nested[file][file_' . $fid_list[1] . '][selected]"]', NULL, 'Second individual file not deleted when the first file is deleted from a multiple file element.');
}
/**
* Ensure that warning is shown if file on the field has been removed.
*/
public function testManagedFileRemoved() {
$this->drupalGet('file/test/1/0/1');
$test_file = $this->getTestFile('text');
$file_field_name = 'files[nested_file][]';
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
$this->drupalPostForm(NULL, $edit, t('Upload'));
$fid = $this->getLastFileId();
$file = \Drupal::entityManager()->getStorage('file')->load($fid);
$file->delete();
$this->drupalPostForm(NULL, $edit, t('Upload'));
// We expect the title 'Managed <em>file & butter</em>' which got escaped
// via a t() call before.
$this->assertRaw('The file referenced by the Managed <em>file &amp; butter</em> field does not exist.');
}
/**
* Ensure a file entity can be saved when the file does not exist on disk.
*/
public function testFileRemovedFromDisk() {
$this->drupalGet('file/test/1/0/1');
$test_file = $this->getTestFile('text');
$file_field_name = 'files[nested_file][]';
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
$this->drupalPostForm(NULL, $edit, t('Upload'));
$this->drupalPostForm(NULL, [], t('Save'));
$fid = $this->getLastFileId();
/** @var $file \Drupal\file\FileInterface */
$file = $this->container->get('entity_type.manager')->getStorage('file')->load($fid);
$file->setPermanent();
$file->save();
$this->assertTrue(file_unmanaged_delete($file->getFileUri()));
$file->save();
$this->assertTrue($file->isPermanent());
$file->delete();
}
/**
* Verify that unused permanent files can be used.
*/
public function testUnusedPermanentFileValidation() {
// Create a permanent file without usages.
$file = $this->getTestFile('image');
$file->setPermanent();
$file->save();
// By default, unused files are no longer marked temporary, and it must be
// allowed to reference an unused file.
$this->drupalGet('file/test/1/0/1/' . $file->id());
$this->drupalPostForm(NULL, [], 'Save');
$this->assertNoText('The file used in the Managed file &amp; butter field may not be referenced.');
$this->assertText('The file ids are ' . $file->id());
// Enable marking unused files as temporary, unused permanent files must not
// be referenced now.
$this->config('file.settings')
->set('make_unused_managed_files_temporary', TRUE)
->save();
$this->drupalGet('file/test/1/0/1/' . $file->id());
$this->drupalPostForm(NULL, [], 'Save');
$this->assertText('The file used in the Managed file &amp; butter field may not be referenced.');
$this->assertNoText('The file ids are ' . $file->id());
// Make the file temporary, now using it is allowed.
$file->setTemporary();
$file->save();
$this->drupalGet('file/test/1/0/1/' . $file->id());
$this->drupalPostForm(NULL, [], 'Save');
$this->assertNoText('The file used in the Managed file &amp; butter field may not be referenced.');
$this->assertText('The file ids are ' . $file->id());
// Make the file permanent again and add a usage from itself, referencing is
// still allowed.
$file->setPermanent();
$file->save();
/** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
$file_usage = \Drupal::service('file.usage');
$file_usage->add($file, 'file', 'file', $file->id());
$this->drupalGet('file/test/1/0/1/' . $file->id());
$this->drupalPostForm(NULL, [], 'Save');
$this->assertNoText('The file used in the Managed file &amp; butter field may not be referenced.');
$this->assertText('The file ids are ' . $file->id());
}
}

View file

@ -0,0 +1,200 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Base class for file tests that use the file_test module to test uploads and
* hooks.
*/
abstract class FileManagedTestBase extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['file_test', 'file'];
protected function setUp() {
parent::setUp();
// Clear out any hook calls.
file_test_reset();
}
/**
* Assert that all of the specified hook_file_* hooks were called once, other
* values result in failure.
*
* @param string[] $expected
* An array of strings containing with the hook name; for example, 'load',
* 'save', 'insert', etc.
*/
public function assertFileHooksCalled($expected) {
\Drupal::state()->resetCache();
// Determine which hooks were called.
$actual = array_keys(array_filter(file_test_get_all_calls()));
// Determine if there were any expected that were not called.
$uncalled = array_diff($expected, $actual);
if (count($uncalled)) {
$this->assertTrue(FALSE, format_string('Expected hooks %expected to be called but %uncalled was not called.', ['%expected' => implode(', ', $expected), '%uncalled' => implode(', ', $uncalled)]));
}
else {
$this->assertTrue(TRUE, format_string('All the expected hooks were called: %expected', ['%expected' => empty($expected) ? '(none)' : implode(', ', $expected)]));
}
// Determine if there were any unexpected calls.
$unexpected = array_diff($actual, $expected);
if (count($unexpected)) {
$this->assertTrue(FALSE, format_string('Unexpected hooks were called: %unexpected.', ['%unexpected' => empty($unexpected) ? '(none)' : implode(', ', $unexpected)]));
}
else {
$this->assertTrue(TRUE, 'No unexpected hooks were called.');
}
}
/**
* Assert that a hook_file_* hook was called a certain number of times.
*
* @param string $hook
* String with the hook name; for instance, 'load', 'save', 'insert', etc.
* @param int $expected_count
* Optional integer count.
* @param string|null $message
* Optional translated string message.
*/
public function assertFileHookCalled($hook, $expected_count = 1, $message = NULL) {
$actual_count = count(file_test_get_calls($hook));
if (!isset($message)) {
if ($actual_count == $expected_count) {
$message = format_string('hook_file_@name was called correctly.', ['@name' => $hook]);
}
elseif ($expected_count == 0) {
$message = \Drupal::translation()->formatPlural($actual_count, 'hook_file_@name was not expected to be called but was actually called once.', 'hook_file_@name was not expected to be called but was actually called @count times.', ['@name' => $hook, '@count' => $actual_count]);
}
else {
$message = format_string('hook_file_@name was expected to be called %expected times but was called %actual times.', ['@name' => $hook, '%expected' => $expected_count, '%actual' => $actual_count]);
}
}
$this->assertEqual($actual_count, $expected_count, $message);
}
/**
* Asserts that two files have the same values (except timestamp).
*
* @param \Drupal\file\FileInterface $before
* File object to compare.
* @param \Drupal\file\FileInterface $after
* File object to compare.
*/
public function assertFileUnchanged(FileInterface $before, FileInterface $after) {
$this->assertEqual($before->id(), $after->id(), t('File id is the same: %file1 == %file2.', ['%file1' => $before->id(), '%file2' => $after->id()]), 'File unchanged');
$this->assertEqual($before->getOwner()->id(), $after->getOwner()->id(), t('File owner is the same: %file1 == %file2.', ['%file1' => $before->getOwner()->id(), '%file2' => $after->getOwner()->id()]), 'File unchanged');
$this->assertEqual($before->getFilename(), $after->getFilename(), t('File name is the same: %file1 == %file2.', ['%file1' => $before->getFilename(), '%file2' => $after->getFilename()]), 'File unchanged');
$this->assertEqual($before->getFileUri(), $after->getFileUri(), t('File path is the same: %file1 == %file2.', ['%file1' => $before->getFileUri(), '%file2' => $after->getFileUri()]), 'File unchanged');
$this->assertEqual($before->getMimeType(), $after->getMimeType(), t('File MIME type is the same: %file1 == %file2.', ['%file1' => $before->getMimeType(), '%file2' => $after->getMimeType()]), 'File unchanged');
$this->assertEqual($before->getSize(), $after->getSize(), t('File size is the same: %file1 == %file2.', ['%file1' => $before->getSize(), '%file2' => $after->getSize()]), 'File unchanged');
$this->assertEqual($before->isPermanent(), $after->isPermanent(), t('File status is the same: %file1 == %file2.', ['%file1' => $before->isPermanent(), '%file2' => $after->isPermanent()]), 'File unchanged');
}
/**
* Asserts that two files are not the same by comparing the fid and filepath.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
*/
public function assertDifferentFile(FileInterface $file1, FileInterface $file2) {
$this->assertNotEqual($file1->id(), $file2->id(), t('Files have different ids: %file1 != %file2.', ['%file1' => $file1->id(), '%file2' => $file2->id()]), 'Different file');
$this->assertNotEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have different paths: %file1 != %file2.', ['%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri()]), 'Different file');
}
/**
* Asserts that two files are the same by comparing the fid and filepath.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
*/
public function assertSameFile(FileInterface $file1, FileInterface $file2) {
$this->assertEqual($file1->id(), $file2->id(), t('Files have the same ids: %file1 == %file2.', ['%file1' => $file1->id(), '%file2-fid' => $file2->id()]), 'Same file');
$this->assertEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have the same path: %file1 == %file2.', ['%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri()]), 'Same file');
}
/**
* Create a file and save it to the files table and assert that it occurs
* correctly.
*
* @param string $filepath
* Optional string specifying the file path. If none is provided then a
* randomly named file will be created in the site's files directory.
* @param string $contents
* Optional contents to save into the file. If a NULL value is provided an
* arbitrary string will be used.
* @param string $scheme
* Optional string indicating the stream scheme to use. Drupal core includes
* public, private, and temporary. The public wrapper is the default.
* @return \Drupal\file\FileInterface
* File entity.
*/
public function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
// Don't count hook invocations caused by creating the file.
\Drupal::state()->set('file_test.count_hook_invocations', FALSE);
$file = File::create([
'uri' => $this->createUri($filepath, $contents, $scheme),
'uid' => 1,
]);
$file->save();
// Write the record directly rather than using the API so we don't invoke
// the hooks.
$this->assertTrue($file->id() > 0, 'The file was added to the database.', 'Create test file');
\Drupal::state()->set('file_test.count_hook_invocations', TRUE);
return $file;
}
/**
* Creates a file and returns its URI.
*
* @param string $filepath
* Optional string specifying the file path. If none is provided then a
* randomly named file will be created in the site's files directory.
* @param string $contents
* Optional contents to save into the file. If a NULL value is provided an
* arbitrary string will be used.
* @param string $scheme
* Optional string indicating the stream scheme to use. Drupal core includes
* public, private, and temporary. The public wrapper is the default.
*
* @return string
* File URI.
*/
public function createUri($filepath = NULL, $contents = NULL, $scheme = NULL) {
if (!isset($filepath)) {
// Prefix with non-latin characters to ensure that all file-related
// tests work with international filenames.
$filepath = 'Файл для тестирования ' . $this->randomMachineName();
}
if (!isset($scheme)) {
$scheme = file_default_scheme();
}
$filepath = $scheme . '://' . $filepath;
if (!isset($contents)) {
$contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.";
}
file_put_contents($filepath, $contents);
$this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
return $filepath;
}
}

View file

@ -0,0 +1,205 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
/**
* Uploads files to translated nodes.
*
* @group file
*/
class FileOnTranslatedEntityTest extends FileFieldTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language', 'content_translation'];
/**
* The name of the file field used in the test.
*
* @var string
*/
protected $fieldName;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// This test expects unused managed files to be marked as temporary a file.
$this->config('file.settings')
->set('make_unused_managed_files_temporary', TRUE)
->save();
// Create the "Basic page" node type.
// @todo Remove the disabling of new revision creation in
// https://www.drupal.org/node/1239558.
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page', 'new_revision' => FALSE]);
// Create a file field on the "Basic page" node type.
$this->fieldName = strtolower($this->randomMachineName());
$this->createFileField($this->fieldName, 'node', 'page');
// Create and log in user.
$permissions = [
'access administration pages',
'administer content translation',
'administer content types',
'administer languages',
'create content translations',
'create page content',
'edit any page content',
'translate any entity',
'delete any page content',
];
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
// Add a second and third language.
$edit = [];
$edit['predefined_langcode'] = 'fr';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
$edit = [];
$edit['predefined_langcode'] = 'nl';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Enable translation for "Basic page" nodes.
$edit = [
'entity_types[node]' => 1,
'settings[node][page][translatable]' => 1,
"settings[node][page][fields][$this->fieldName]" => 1,
];
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
\Drupal::entityManager()->clearCachedDefinitions();
}
/**
* Tests synced file fields on translated nodes.
*/
public function testSyncedFiles() {
// Verify that the file field on the "Basic page" node type is translatable.
$definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
$this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node file field is translatable.');
// Create a default language node.
$default_language_node = $this->drupalCreateNode(['type' => 'page', 'title' => 'Lost in translation']);
// Edit the node to upload a file.
$edit = [];
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[0]->uri);
$this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
$first_fid = $this->getLastFileId();
// Translate the node into French: remove the existing file.
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/fr', [], t('Remove'));
// Upload a different file.
$edit = [];
$edit['title[0][value]'] = 'Bill Murray';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[1]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
// This inspects the HTML after the post of the translation, the file
// should be displayed on the original node.
$this->assertRaw('file--mime-text-plain');
$second_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
/* @var $file \Drupal\file\FileInterface */
// Ensure the file status of the first file permanent.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the second file is permanent.
$file = File::load($second_fid);
$this->assertTrue($file->isPermanent());
// Translate the node into dutch: remove the existing file.
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/nl', [], t('Remove'));
// Upload a different file.
$edit = [];
$edit['title[0][value]'] = 'Scarlett Johansson';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[2]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$third_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first file is untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
// This inspects the HTML after the post of the translation, the file
// should be displayed on the original node.
$this->assertRaw('file--mime-text-plain');
// Ensure the file status of the second file is permanent.
$file = File::load($second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the third file is permanent.
$file = File::load($third_fid);
$this->assertTrue($file->isPermanent());
// Edit the second translation: remove the existing file.
$this->drupalPostForm('fr/node/' . $default_language_node->id() . '/edit', [], t('Remove'));
// Upload a different file.
$edit = [];
$edit['title[0][value]'] = 'David Bowie';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[3]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$replaced_second_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first and third files are untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
$file = File::load($third_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the replaced second file is permanent.
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isPermanent());
// Delete the third translation.
$this->drupalPostForm('nl/node/' . $default_language_node->id() . '/delete', [], t('Delete Dutch translation'));
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first and replaced second files are untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the third file is now temporary.
$file = File::load($third_fid);
$this->assertTrue($file->isTemporary());
// Delete the all translations.
$this->drupalPostForm('node/' . $default_language_node->id() . '/delete', [], t('Delete all translations'));
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the file status of the all files are now temporary.
$file = File::load($first_fid);
$this->assertTrue($file->isTemporary(), 'First file still exists and is temporary.');
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isTemporary());
}
}

View file

@ -0,0 +1,232 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraint;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\file\Entity\File;
use Drupal\node\Entity\NodeType;
use Drupal\user\RoleInterface;
/**
* Uploads a test to a private node and checks access.
*
* @group file
*/
class FilePrivateTest extends FileFieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_access_test', 'field_test'];
protected function setUp() {
parent::setUp();
node_access_test_add_field(NodeType::load('article'));
node_access_rebuild();
\Drupal::state()->set('node_access_test.private', TRUE);
// This test expects unused managed files to be marked as a temporary file.
$this->config('file.settings')
->set('make_unused_managed_files_temporary', TRUE)
->save();
}
/**
* Tests file access for file uploaded to a private node.
*/
public function testPrivateFile() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$type_name = 'article';
$field_name = strtolower($this->randomMachineName());
$this->createFileField($field_name, 'node', $type_name, ['uri_scheme' => 'private']);
$test_file = $this->getTestFile('text');
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name, TRUE, ['private' => TRUE]);
\Drupal::entityManager()->getStorage('node')->resetCache([$nid]);
/* @var \Drupal\node\NodeInterface $node */
$node = $node_storage->load($nid);
$node_file = File::load($node->{$field_name}->target_id);
// Ensure the file can be viewed.
$this->drupalGet('node/' . $node->id());
$this->assertRaw($node_file->getFilename(), 'File reference is displayed after attaching it');
// Ensure the file can be downloaded.
$this->drupalGet(file_create_url($node_file->getFileUri()));
$this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
$this->drupalLogOut();
$this->drupalGet(file_create_url($node_file->getFileUri()));
$this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.');
// Create a field with no view access. See
// field_test_entity_field_access().
$no_access_field_name = 'field_no_view_access';
$this->createFileField($no_access_field_name, 'node', $type_name, ['uri_scheme' => 'private']);
// Test with the field that should deny access through field access.
$this->drupalLogin($this->adminUser);
$nid = $this->uploadNodeFile($test_file, $no_access_field_name, $type_name, TRUE, ['private' => TRUE]);
\Drupal::entityManager()->getStorage('node')->resetCache([$nid]);
$node = $node_storage->load($nid);
$node_file = File::load($node->{$no_access_field_name}->target_id);
// Ensure the file cannot be downloaded.
$file_url = file_create_url($node_file->getFileUri());
$this->drupalGet($file_url);
$this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission.');
// Attempt to reuse the file when editing a node.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
// Can't use drupalPostForm() to set hidden fields.
$this->drupalGet('node/' . $new_node->id() . '/edit');
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
$this->getSession()->getPage()->pressButton(t('Save'));
// Make sure the form submit failed - we stayed on the edit form.
$this->assertUrl('node/' . $new_node->id() . '/edit');
// Check that we got the expected constraint form error.
$constraint = new ReferenceAccessConstraint();
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
// Attempt to reuse the existing file when creating a new node, and confirm
// that access is still denied.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
// Can't use drupalPostForm() to set hidden fields.
$this->drupalGet('node/add/' . $type_name);
$this->getSession()->getPage()->find('css', 'input[name="title[0][value]"]')->setValue($edit['title[0][value]']);
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
$this->getSession()->getPage()->pressButton(t('Save'));
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertTrue(empty($new_node), 'Node was not created.');
$this->assertUrl('node/add/' . $type_name);
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
// Now make file_test_file_download() return everything.
\Drupal::state()->set('file_test.allow_all', TRUE);
// Delete the node.
$node->delete();
// Ensure the file can still be downloaded by the owner.
$this->drupalGet($file_url);
$this->assertResponse(200, 'Confirmed that the owner still has access to the temporary file.');
// Ensure the file cannot be downloaded by an anonymous user.
$this->drupalLogout();
$this->drupalGet($file_url);
$this->assertResponse(403, 'Confirmed that access is denied for an anonymous user to the temporary file.');
// Ensure the file cannot be downloaded by another user.
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
$this->drupalGet($file_url);
$this->assertResponse(403, 'Confirmed that access is denied for another user to the temporary file.');
// As an anonymous user, create a temporary file with no references and
// confirm that only the session that uploaded it may view it.
$this->drupalLogout();
user_role_change_permissions(
RoleInterface::ANONYMOUS_ID,
[
"create $type_name content" => TRUE,
'access content' => TRUE,
]
);
$test_file = $this->getTestFile('text');
$this->drupalGet('node/add/' . $type_name);
$edit = ['files[' . $field_name . '_0]' => $file_system->realpath($test_file->getFileUri())];
$this->drupalPostForm(NULL, $edit, t('Upload'));
/** @var \Drupal\file\FileStorageInterface $file_storage */
$file_storage = $this->container->get('entity.manager')->getStorage('file');
$files = $file_storage->loadByProperties(['uid' => 0]);
$this->assertEqual(1, count($files), 'Loaded one anonymous file.');
$file = end($files);
$this->assertTrue($file->isTemporary(), 'File is temporary.');
$usage = $this->container->get('file.usage')->listUsage($file);
$this->assertFalse($usage, 'No file usage found.');
$file_url = file_create_url($file->getFileUri());
$this->drupalGet($file_url);
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.');
// Close the prior connection and remove the session cookie.
$this->getSession()->reset();
$this->drupalGet($file_url);
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.');
// As an anonymous user, create a permanent file, then remove all
// references to the file (so that it becomes temporary again) and confirm
// that only the session that uploaded it may view it.
$test_file = $this->getTestFile('text');
$this->drupalGet('node/add/' . $type_name);
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$edit['files[' . $field_name . '_0]'] = $file_system->realpath($test_file->getFileUri());
$this->drupalPostForm(NULL, $edit, t('Save'));
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$file_id = $new_node->{$field_name}->target_id;
$file = File::load($file_id);
$this->assertTrue($file->isPermanent(), 'File is permanent.');
// Remove the reference to this file.
$new_node->{$field_name} = [];
$new_node->save();
$file = File::load($file_id);
$this->assertTrue($file->isTemporary(), 'File is temporary.');
$usage = $this->container->get('file.usage')->listUsage($file);
$this->assertFalse($usage, 'No file usage found.');
$file_url = file_create_url($file->getFileUri());
$this->drupalGet($file_url);
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the file whose references were removed.');
// Close the prior connection and remove the session cookie.
$this->getSession()->reset();
$this->drupalGet($file_url);
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the file whose references were removed.');
// As an anonymous user, create a permanent file that is referenced by a
// published node and confirm that all anonymous users may view it.
$test_file = $this->getTestFile('text');
$this->drupalGet('node/add/' . $type_name);
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$edit['files[' . $field_name . '_0]'] = $file_system->realpath($test_file->getFileUri());
$this->drupalPostForm(NULL, $edit, t('Save'));
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$file = File::load($new_node->{$field_name}->target_id);
$this->assertTrue($file->isPermanent(), 'File is permanent.');
$usage = $this->container->get('file.usage')->listUsage($file);
$this->assertTrue($usage, 'File usage found.');
$file_url = file_create_url($file->getFileUri());
$this->drupalGet($file_url);
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.');
// Close the prior connection and remove the session cookie.
$this->getSession()->reset();
$this->drupalGet($file_url);
$this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.');
// As an anonymous user, create a permanent file that is referenced by an
// unpublished node and confirm that no anonymous users may view it (even
// the session that uploaded the file) because they cannot view the
// unpublished node.
$test_file = $this->getTestFile('text');
$this->drupalGet('node/add/' . $type_name);
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$edit['files[' . $field_name . '_0]'] = $file_system->realpath($test_file->getFileUri());
$this->drupalPostForm(NULL, $edit, t('Save'));
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$new_node->setUnpublished();
$new_node->save();
$file = File::load($new_node->{$field_name}->target_id);
$this->assertTrue($file->isPermanent(), 'File is permanent.');
$usage = $this->container->get('file.usage')->listUsage($file);
$this->assertTrue($usage, 'File usage found.');
$file_url = file_create_url($file->getFileUri());
$this->drupalGet($file_url);
$this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.');
// Close the prior connection and remove the session cookie.
$this->getSession()->reset();
$this->drupalGet($file_url);
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.');
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\file\Entity\File;
/**
* Generates text using placeholders for dummy content to check file token
* replacement.
*
* @group file
*/
class FileTokenReplaceTest extends FileFieldTestBase {
/**
* Creates a file, then tests the tokens generated from it.
*/
public function testFileTokenReplacement() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$token_service = \Drupal::token();
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Create file field.
$type_name = 'article';
$field_name = 'field_' . strtolower($this->randomMachineName());
$this->createFileField($field_name, 'node', $type_name);
$test_file = $this->getTestFile('text');
// Coping a file to test uploads with non-latin filenames.
$filename = drupal_dirname($test_file->getFileUri()) . '/текстовый файл.txt';
$test_file = file_copy($test_file, $filename);
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
// Load the node and the file.
$node_storage->resetCache([$nid]);
$node = $node_storage->load($nid);
$file = File::load($node->{$field_name}->target_id);
// Generate and test sanitized tokens.
$tests = [];
$tests['[file:fid]'] = $file->id();
$tests['[file:name]'] = Html::escape($file->getFilename());
$tests['[file:path]'] = Html::escape($file->getFileUri());
$tests['[file:mime]'] = Html::escape($file->getMimeType());
$tests['[file:size]'] = format_size($file->getSize());
$tests['[file:url]'] = Html::escape(file_create_url($file->getFileUri()));
$tests['[file:created]'] = format_date($file->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
$tests['[file:created:short]'] = format_date($file->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
$tests['[file:changed]'] = format_date($file->getChangedTime(), 'medium', '', NULL, $language_interface->getId());
$tests['[file:changed:short]'] = format_date($file->getChangedTime(), 'short', '', NULL, $language_interface->getId());
$tests['[file:owner]'] = Html::escape($this->adminUser->getDisplayName());
$tests['[file:owner:uid]'] = $file->getOwnerId();
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($file);
$metadata_tests = [];
$metadata_tests['[file:fid]'] = $base_bubbleable_metadata;
$metadata_tests['[file:name]'] = $base_bubbleable_metadata;
$metadata_tests['[file:path]'] = $base_bubbleable_metadata;
$metadata_tests['[file:mime]'] = $base_bubbleable_metadata;
$metadata_tests['[file:size]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[file:url]'] = $bubbleable_metadata->addCacheContexts(['url.site']);
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[file:created]'] = $bubbleable_metadata->addCacheTags(['rendered']);
$metadata_tests['[file:created:short]'] = $bubbleable_metadata;
$metadata_tests['[file:changed]'] = $bubbleable_metadata;
$metadata_tests['[file:changed:short]'] = $bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[file:owner]'] = $bubbleable_metadata->addCacheTags(['user:2']);
$metadata_tests['[file:owner:uid]'] = $bubbleable_metadata;
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
foreach ($tests as $input => $expected) {
$bubbleable_metadata = new BubbleableMetadata();
$output = $token_service->replace($input, ['file' => $file], ['langcode' => $language_interface->getId()], $bubbleable_metadata);
$this->assertEqual($output, $expected, format_string('Sanitized file token %token replaced.', ['%token' => $input]));
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
}
// Generate and test unsanitized tokens.
$tests['[file:name]'] = $file->getFilename();
$tests['[file:path]'] = $file->getFileUri();
$tests['[file:mime]'] = $file->getMimeType();
$tests['[file:size]'] = format_size($file->getSize());
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, ['file' => $file], ['langcode' => $language_interface->getId(), 'sanitize' => FALSE]);
$this->assertEqual($output, $expected, format_string('Unsanitized file token %token replaced.', ['%token' => $input]));
}
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\FileUploadResourceTestBase;
/**
* @group file
*/
class FileUploadJsonBasicAuthTest extends FileUploadResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\FileUploadResourceTestBase;
/**
* @group file
*/
class FileUploadJsonCookieTest extends FileUploadResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\Tests\file\Functional\Formatter;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\file\Entity\File;
/**
* @coversDefaultClass \Drupal\file\Plugin\Field\FieldFormatter\FileAudioFormatter
* @group file
*/
class FileAudioFormatterTest extends FileMediaFormatterTestBase {
/**
* @covers ::viewElements
*
* @dataProvider dataProvider
*/
public function testRender($tag_count, $formatter_settings) {
$field_config = $this->createMediaField('file_audio', 'mp3', $formatter_settings);
file_put_contents('public://file.mp3', str_repeat('t', 10));
$file1 = File::create([
'uri' => 'public://file.mp3',
'filename' => 'file.mp3',
]);
$file1->save();
$file2 = File::create([
'uri' => 'public://file.mp3',
'filename' => 'file.mp3',
]);
$file2->save();
$entity = EntityTest::create([
$field_config->getName() => [
[
'target_id' => $file1->id(),
],
[
'target_id' => $file2->id(),
],
],
]);
$entity->save();
$this->drupalGet($entity->toUrl());
$file1_url = file_url_transform_relative(file_create_url($file1->getFileUri()));
$file2_url = file_url_transform_relative(file_create_url($file2->getFileUri()));
$assert_session = $this->assertSession();
$assert_session->elementsCount('css', 'audio[controls="controls"]', $tag_count);
$assert_session->elementExists('css', "audio > source[src='$file1_url'][type='audio/mpeg']");
$assert_session->elementExists('css', "audio > source[src='$file2_url'][type='audio/mpeg']");
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\Tests\file\Functional\Formatter;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\BrowserTestBase;
/**
* Provides methods specifically for testing File module's media formatter's.
*/
abstract class FileMediaFormatterTestBase extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'field',
'file',
'user',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['view test entity']));
}
/**
* Creates a file field and set's the correct formatter.
*
* @param string $formatter
* The formatter ID.
* @param string $file_extensions
* The file extensions of the new field.
* @param array $formatter_settings
* Settings for the formatter.
*
* @return \Drupal\field\Entity\FieldConfig
* Newly created file field.
*/
protected function createMediaField($formatter, $file_extensions, array $formatter_settings = []) {
$entity_type = $bundle = 'entity_test';
$field_name = mb_strtolower($this->randomMachineName());
FieldStorageConfig::create([
'entity_type' => $entity_type,
'field_name' => $field_name,
'type' => 'file',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
])->save();
$field_config = FieldConfig::create([
'entity_type' => $entity_type,
'field_name' => $field_name,
'bundle' => $bundle,
'settings' => [
'file_extensions' => trim($file_extensions),
],
]);
$field_config->save();
$display = entity_get_display('entity_test', 'entity_test', 'full');
$display->setComponent($field_name, [
'type' => $formatter,
'settings' => $formatter_settings,
])->save();
return $field_config;
}
/**
* Data provider for testRender.
*
* @return array
* An array of data arrays.
* The data array contains:
* - The number of expected HTML tags.
* - An array of settings for the field formatter.
*/
public function dataProvider() {
return [
[2, []],
[1, ['multiple_file_display_type' => 'sources']],
];
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\Tests\file\Functional\Formatter;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\file\Entity\File;
/**
* @coversDefaultClass \Drupal\file\Plugin\Field\FieldFormatter\FileVideoFormatter
* @group file
*/
class FileVideoFormatterTest extends FileMediaFormatterTestBase {
/**
* @covers ::viewElements
*
* @dataProvider dataProvider
*/
public function testRender($tag_count, $formatter_settings) {
$field_config = $this->createMediaField('file_video', 'mp4', $formatter_settings);
file_put_contents('public://file.mp4', str_repeat('t', 10));
$file1 = File::create([
'uri' => 'public://file.mp4',
'filename' => 'file.mp4',
]);
$file1->save();
$file2 = File::create([
'uri' => 'public://file.mp4',
'filename' => 'file.mp4',
]);
$file2->save();
$entity = EntityTest::create([
$field_config->getName() => [
[
'target_id' => $file1->id(),
],
[
'target_id' => $file2->id(),
],
],
]);
$entity->save();
$this->drupalGet($entity->toUrl());
$file1_url = file_url_transform_relative(file_create_url($file1->getFileUri()));
$file2_url = file_url_transform_relative(file_create_url($file2->getFileUri()));
$assert_session = $this->assertSession();
$assert_session->elementsCount('css', 'video[controls="controls"]', $tag_count);
$assert_session->elementExists('css', "video > source[src='$file1_url'][type='video/mp4']");
$assert_session->elementExists('css', "video > source[src='$file2_url'][type='video/mp4']");
}
}

View file

@ -0,0 +1,139 @@
<?php
namespace Drupal\Tests\file\Functional\Hal;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\file\Functional\Rest\FileResourceTestBase;
use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group hal
*/
class FileHalJsonAnonTest extends FileResourceTestBase {
use HalEntityNormalizationTrait;
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$default_normalization = parent::getExpectedNormalizedEntity();
$normalization = $this->applyHalFieldNormalization($default_normalization);
$url = file_create_url($this->entity->getFileUri());
// @see \Drupal\Tests\hal\Functional\EntityResource\File\FileHalJsonAnonTest::testGetBcUriField()
if ($this->config('hal.settings')->get('bc_file_uri_as_url_normalizer')) {
$normalization['uri'][0]['value'] = $url;
}
$uid = $this->author->id();
return $normalization + [
'_embedded' => [
$this->baseUrl . '/rest/relation/file/file/uid' => [
[
'_links' => [
'self' => [
'href' => $this->baseUrl . "/user/$uid?_format=hal_json",
],
'type' => [
'href' => $this->baseUrl . '/rest/type/user/user',
],
],
'uuid' => [
[
'value' => $this->author->uuid(),
],
],
],
],
],
'_links' => [
'self' => [
'href' => $url,
],
'type' => [
'href' => $this->baseUrl . '/rest/type/file/file',
],
$this->baseUrl . '/rest/relation/file/file/uid' => [
[
'href' => $this->baseUrl . "/user/$uid?_format=hal_json",
],
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return parent::getNormalizedPostEntity() + [
'_links' => [
'type' => [
'href' => $this->baseUrl . '/rest/type/file/file',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheTags() {
return Cache::mergeTags(parent::getExpectedCacheTags(), ['config:hal.settings']);
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'url.site',
'user.permissions',
];
}
/**
* @see hal_update_8501()
*/
public function testGetBcUriField() {
$this->config('hal.settings')->set('bc_file_uri_as_url_normalizer', TRUE)->save(TRUE);
$this->initAuthentication();
$url = $this->getEntityResourceUrl();
$url->setOption('query', ['_format' => static::$format]);
$request_options = $this->getAuthenticationRequestOptions('GET');
$this->provisionEntityResource();
$this->setUpAuthorization('GET');
$response = $this->request('GET', $url, $request_options);
$expected = $this->getExpectedNormalizedEntity();
static::recursiveKSort($expected);
$actual = $this->serializer->decode((string) $response->getBody(), static::$format);
static::recursiveKSort($actual);
$this->assertSame($expected, $actual);
// Explicitly assert that $file->uri->value is an absolute file URL, unlike
// the default normalization.
$this->assertSame($this->baseUrl . '/' . $this->siteDirectory . '/files/drupal.txt', $actual['uri'][0]['value']);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\file\Functional\Hal;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class FileHalJsonBasicAuthTest extends FileHalJsonAnonTest {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,19 @@
<?php
namespace Drupal\Tests\file\Functional\Hal;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class FileHalJsonCookieTest extends FileHalJsonAnonTest {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\file\Functional\Hal;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class FileUploadHalJsonBasicAuthTest extends FileUploadHalJsonTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,19 @@
<?php
namespace Drupal\Tests\file\Functional\Hal;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class FileUploadHalJsonCookieTest extends FileUploadHalJsonTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,97 @@
<?php
namespace Drupal\Tests\file\Functional\Hal;
use Drupal\Tests\rest\Functional\FileUploadResourceTestBase;
use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
/**
* Tests binary data file upload route for HAL JSON.
*/
abstract class FileUploadHalJsonTestBase extends FileUploadResourceTestBase {
use HalEntityNormalizationTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity($fid = 1, $expected_filename = 'example.txt', $expected_as_filename = FALSE) {
$normalization = parent::getExpectedNormalizedEntity($fid, $expected_filename, $expected_as_filename);
// Cannot use applyHalFieldNormalization() as it uses the $entity property
// from the test class, which in the case of file upload tests, is the
// parent entity test entity for the file that's created.
// The HAL normalization adds entity reference fields to '_links' and
// '_embedded'.
unset($normalization['uid']);
return $normalization + [
'_links' => [
'self' => [
// @todo This can use a proper link once
// https://www.drupal.org/project/drupal/issues/2907402 is complete.
// This link matches what is generated from from File::url(), a
// resource URL is currently not available.
'href' => file_create_url($normalization['uri'][0]['value']),
],
'type' => [
'href' => $this->baseUrl . '/rest/type/file/file',
],
$this->baseUrl . '/rest/relation/file/file/uid' => [
['href' => $this->baseUrl . '/user/' . $this->account->id() . '?_format=hal_json'],
],
],
'_embedded' => [
$this->baseUrl . '/rest/relation/file/file/uid' => [
[
'_links' => [
'self' => [
'href' => $this->baseUrl . '/user/' . $this->account->id() . '?_format=hal_json',
],
'type' => [
'href' => $this->baseUrl . '/rest/type/user/user',
],
],
'uuid' => [
[
'value' => $this->account->uuid(),
],
],
],
],
],
];
}
/**
* {@inheritdoc}
*
* @see \Drupal\Tests\hal\Functional\EntityResource\EntityTest\EntityTestHalJsonAnonTest::getNormalizedPostEntity()
*/
protected function getNormalizedPostEntity() {
return parent::getNormalizedPostEntity() + [
'_links' => [
'type' => [
'href' => $this->baseUrl . '/rest/type/entity_test/entity_test',
],
],
];
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests multiple file upload.
*
* @group file
*/
class MultipleFileUploadTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['file'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin = $this->drupalCreateUser(['administer themes']);
$this->drupalLogin($admin);
}
/**
* Tests multiple file field with all file extensions.
*/
public function testMultipleFileFieldWithAllFileExtensions() {
$theme = 'test_theme_settings';
\Drupal::service('theme_handler')->install([$theme]);
$this->drupalGet("admin/appearance/settings/$theme");
$edit = [];
// Create few files with non-typical extensions.
foreach (['file1.wtf', 'file2.wtf'] as $i => $file) {
$file_path = $this->root . "/sites/default/files/simpletest/$file";
file_put_contents($file_path, 'File with non-default extension.', FILE_APPEND | LOCK_EX);
$edit["files[multi_file][$i]"] = $file_path;
}
// @todo: Replace after https://www.drupal.org/project/drupal/issues/2917885
$this->drupalGet("admin/appearance/settings/$theme");
$submit_xpath = $this->assertSession()->buttonExists('Save configuration')->getXpath();
$client = $this->getSession()->getDriver()->getClient();
$form = $client->getCrawler()->filterXPath($submit_xpath)->form();
$client->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $edit);
$page = $this->getSession()->getPage();
$this->assertNotContains('Only files with the following extensions are allowed', $page->getContent());
$this->assertContains('The configuration options have been saved.', $page->getContent());
$this->assertContains('file1.wtf', $page->getContent());
$this->assertContains('file2.wtf', $page->getContent());
}
}

View file

@ -0,0 +1,125 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
use Drupal\node\Entity\Node;
/**
* Uploads private files to translated node and checks access.
*
* @group file
*/
class PrivateFileOnTranslatedEntityTest extends FileFieldTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language', 'content_translation'];
/**
* The name of the file field used in the test.
*
* @var string
*/
protected $fieldName;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create the "Basic page" node type.
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
// Create a file field on the "Basic page" node type.
$this->fieldName = strtolower($this->randomMachineName());
$this->createFileField($this->fieldName, 'node', 'page', ['uri_scheme' => 'private']);
// Create and log in user.
$permissions = [
'access administration pages',
'administer content translation',
'administer content types',
'administer languages',
'create content translations',
'create page content',
'edit any page content',
'translate any entity',
];
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
// Add a second language.
$edit = [];
$edit['predefined_langcode'] = 'fr';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Enable translation for "Basic page" nodes.
$edit = [
'entity_types[node]' => 1,
'settings[node][page][translatable]' => 1,
"settings[node][page][fields][$this->fieldName]" => 1,
];
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
\Drupal::entityManager()->clearCachedDefinitions();
}
/**
* Tests private file fields on translated nodes.
*/
public function testPrivateLanguageFile() {
// Verify that the file field on the "Basic page" node type is translatable.
$definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
$this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node file field is translatable.');
// Create a default language node.
$default_language_node = $this->drupalCreateNode(['type' => 'page']);
// Edit the node to upload a file.
$edit = [];
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[0]->uri);
$this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
$last_fid_prior = $this->getLastFileId();
// Languages are cached on many levels, and we need to clear those caches.
$this->rebuildContainer();
// Ensure the file can be downloaded.
\Drupal::entityManager()->getStorage('node')->resetCache([$default_language_node->id()]);
$node = Node::load($default_language_node->id());
$node_file = File::load($node->{$this->fieldName}->target_id);
$this->drupalGet(file_create_url($node_file->getFileUri()));
$this->assertResponse(200, 'Confirmed that the file attached to the English node can be downloaded.');
// Translate the node into French.
$this->drupalGet('node/' . $default_language_node->id() . '/translations');
$this->clickLink(t('Add'));
// Remove the existing file.
$this->drupalPostForm(NULL, [], t('Remove'));
// Upload a different file.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[1]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$last_fid = $this->getLastFileId();
// Verify the translation was created.
\Drupal::entityManager()->getStorage('node')->resetCache([$default_language_node->id()]);
$default_language_node = Node::load($default_language_node->id());
$this->assertTrue($default_language_node->hasTranslation('fr'), 'Node found in database.');
$this->assertTrue($last_fid > $last_fid_prior, 'New file got saved.');
// Ensure the file attached to the translated node can be downloaded.
$french_node = $default_language_node->getTranslation('fr');
$node_file = File::load($french_node->{$this->fieldName}->target_id);
$this->drupalGet(file_create_url($node_file->getFileUri()));
$this->assertResponse(200, 'Confirmed that the file attached to the French node can be downloaded.');
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\file\Functional;
/**
* Tests the file uploading functions.
*
* @group file
*/
class RemoteFileSaveUploadTest extends SaveUploadTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['file_test'];
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\file\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class FileJsonAnonTest extends FileResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\file\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class FileJsonBasicAuthTest extends FileResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\file\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class FileJsonCookieTest extends FileResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,233 @@
<?php
namespace Drupal\Tests\file\Functional\Rest;
use Drupal\file\Entity\File;
use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\User;
abstract class FileResourceTestBase extends EntityResourceTestBase {
use BcTimestampNormalizerUnixTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['file', 'user'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'file';
/**
* @var \Drupal\file\FileInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [
'uri' => NULL,
'filemime' => NULL,
'filesize' => NULL,
'status' => NULL,
'changed' => NULL,
];
/**
* @var \Drupal\user\UserInterface
*/
protected $author;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['access content']);
break;
case 'PATCH':
case 'DELETE':
// \Drupal\file\FileAccessControlHandler::checkAccess() grants 'update'
// and 'delete' access only to the user that owns the file. So there is
// no permission to grant: instead, the file owner must be changed from
// its default (user 1) to the current user.
$this->makeCurrentUserFileOwner();
break;
}
}
/**
* {@inheritdoc}
*/
protected function grantPermissionsToTestedRole(array $permissions) {
// testPatch() and testDelete() test the 'bc_entity_resource_permissions' BC
// layer; also call makeCurrentUserFileOwner() then.
if ($permissions === ['restful patch entity:file'] || $permissions === ['restful delete entity:file']) {
$this->makeCurrentUserFileOwner();
}
parent::grantPermissionsToTestedRole($permissions);
}
/**
* Makes the current user the file owner.
*/
protected function makeCurrentUserFileOwner() {
$account = static::$auth ? User::load(2) : User::load(0);
$this->entity->setOwnerId($account->id());
$this->entity->setOwner($account);
$this->entity->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$this->author = User::load(1);
$file = File::create();
$file->setOwnerId($this->author->id());
$file->setFilename('drupal.txt');
$file->setMimeType('text/plain');
$file->setFileUri('public://drupal.txt');
$file->set('status', FILE_STATUS_PERMANENT);
$file->save();
file_put_contents($file->getFileUri(), 'Drupal');
return $file;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'changed' => [
$this->formatExpectedTimestampItemValues($this->entity->getChangedTime()),
],
'created' => [
$this->formatExpectedTimestampItemValues((int) $this->entity->getCreatedTime()),
],
'fid' => [
[
'value' => 1,
],
],
'filemime' => [
[
'value' => 'text/plain',
],
],
'filename' => [
[
'value' => 'drupal.txt',
],
],
'filesize' => [
[
'value' => (int) $this->entity->getSize(),
],
],
'langcode' => [
[
'value' => 'en',
],
],
'status' => [
[
'value' => TRUE,
],
],
'uid' => [
[
'target_id' => (int) $this->author->id(),
'target_type' => 'user',
'target_uuid' => $this->author->uuid(),
'url' => base_path() . 'user/' . $this->author->id(),
],
],
'uri' => [
[
'url' => base_path() . $this->siteDirectory . '/files/drupal.txt',
'value' => 'public://drupal.txt',
],
],
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'uid' => [
[
'target_id' => $this->author->id(),
],
],
'filename' => [
[
'value' => 'drupal.txt',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPatchEntity() {
return array_diff_key($this->getNormalizedPostEntity(), ['uid' => TRUE]);
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'user.permissions',
];
}
/**
* {@inheritdoc}
*/
public function testPost() {
// Drupal does not allow creating file entities independently. It allows you
// to create file entities that are referenced from another entity (e.g. an
// image for a node's image field).
// For that purpose, there is the "file_upload" REST resource plugin.
// @see \Drupal\file\FileAccessControlHandler::checkCreateAccess()
// @see \Drupal\file\Plugin\rest\resource\FileUploadResource
$this->markTestSkipped();
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
if ($method === 'GET') {
return "The 'access content' permission is required.";
}
if ($method === 'PATCH' || $method === 'DELETE') {
return 'Only the file owner can update or delete the file entity.';
}
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Drupal\Tests\file\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class FileXmlAnonTest extends FileResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\file\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class FileXmlBasicAuthTest extends FileResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\Tests\file\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class FileXmlCookieTest extends FileResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,494 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the _file_save_upload_from_form() function.
*
* @group file
*
* @see _file_save_upload_from_form()
*/
class SaveUploadFormTest extends FileManagedTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['dblog'];
/**
* An image file path for uploading.
*
* @var \Drupal\file\FileInterface
*/
protected $image;
/**
* A PHP file path for upload security testing.
*/
protected $phpfile;
/**
* The largest file id when the test starts.
*/
protected $maxFidBefore;
/**
* Extension of the image filename.
*
* @var string
*/
protected $imageExtension;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$account = $this->drupalCreateUser(['access site reports']);
$this->drupalLogin($account);
$image_files = $this->drupalGetTestFiles('image');
$this->image = File::create((array) current($image_files));
list(, $this->imageExtension) = explode('.', $this->image->getFilename());
$this->assertTrue(is_file($this->image->getFileUri()), "The image file we're going to upload exists.");
$this->phpfile = current($this->drupalGetTestFiles('php'));
$this->assertTrue(is_file($this->phpfile->uri), 'The PHP file we are going to upload exists.');
$this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
// Upload with replace to guarantee there's something there.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called then clean out the hook
// counters.
$this->assertFileHooksCalled(['validate', 'insert']);
file_test_reset();
}
/**
* Tests the _file_save_upload_from_form() function.
*/
public function testNormal() {
$max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
$this->assertTrue($max_fid_after > $this->maxFidBefore, 'A new file was created.');
$file1 = File::load($max_fid_after);
$this->assertTrue($file1, 'Loaded the file.');
// MIME type of the uploaded image may be either image/jpeg or image/png.
$this->assertEqual(substr($file1->getMimeType(), 0, 5), 'image', 'A MIME type was set.');
// Reset the hook counters to get rid of the 'load' we just called.
file_test_reset();
// Upload a second file.
$image2 = current($this->drupalGetTestFiles('image'));
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$edit = ['files[file_test_upload][]' => $file_system->realpath($image2->uri)];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'));
$max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
$file2 = File::load($max_fid_after);
$this->assertTrue($file2, 'Loaded the file');
// MIME type of the uploaded image may be either image/jpeg or image/png.
$this->assertEqual(substr($file2->getMimeType(), 0, 5), 'image', 'A MIME type was set.');
// Load both files using File::loadMultiple().
$files = File::loadMultiple([$file1->id(), $file2->id()]);
$this->assertTrue(isset($files[$file1->id()]), 'File was loaded successfully');
$this->assertTrue(isset($files[$file2->id()]), 'File was loaded successfully');
// Upload a third file to a subdirectory.
$image3 = current($this->drupalGetTestFiles('image'));
$image3_realpath = $file_system->realpath($image3->uri);
$dir = $this->randomMachineName();
$edit = [
'files[file_test_upload][]' => $image3_realpath,
'file_subdir' => $dir,
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'));
$this->assertTrue(is_file('temporary://' . $dir . '/' . trim(drupal_basename($image3_realpath))));
}
/**
* Tests extension handling.
*/
public function testHandleExtension() {
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
// The file being tested is a .gif which is in the default safe list
// of extensions to allow when the extension validator isn't used. This is
// implicitly tested at the testNormal() test. Here we tell
// _file_save_upload_from_form() to only allow ".foo".
$extensions = 'foo';
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'extensions' => $extensions,
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$message = t('Only files with the following extensions are allowed:') . ' <em class="placeholder">' . $extensions . '</em>';
$this->assertRaw($message, 'Cannot upload a disallowed extension');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate']);
// Reset the hook counters.
file_test_reset();
$extensions = 'foo ' . $this->imageExtension;
// Now tell _file_save_upload_from_form() to allow the extension of our test image.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'extensions' => $extensions,
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('Only files with the following extensions are allowed:'), 'Can upload an allowed extension.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'load', 'update']);
// Reset the hook counters.
file_test_reset();
// Now tell _file_save_upload_from_form() to allow any extension.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'allow_all_extensions' => TRUE,
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('Only files with the following extensions are allowed:'), 'Can upload any extension.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'load', 'update']);
}
/**
* Tests dangerous file handling.
*/
public function testHandleDangerousFile() {
$config = $this->config('system.file');
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
// Allow the .php extension and make sure it gets renamed to .txt for
// safety. Also check to make sure its MIME type was changed.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload][]' => $file_system->realpath($this->phpfile->uri),
'is_image_file' => FALSE,
'extensions' => 'php',
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$message = t('For security reasons, your upload has been renamed to') . ' <em class="placeholder">' . $this->phpfile->filename . '.txt' . '</em>';
$this->assertRaw($message, 'Dangerous file was renamed.');
$this->assertRaw(t('File MIME type is text/plain.'), "Dangerous file's MIME type was changed.");
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
// Ensure dangerous files are not renamed when insecure uploads is TRUE.
// Turn on insecure uploads.
$config->set('allow_insecure_uploads', 1)->save();
// Reset the hook counters.
file_test_reset();
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found no security message.');
$this->assertRaw(t('File name is @filename', ['@filename' => $this->phpfile->filename]), 'Dangerous file was not renamed when insecure uploads is TRUE.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
// Turn off insecure uploads.
$config->set('allow_insecure_uploads', 0)->save();
}
/**
* Tests file munge handling.
*/
public function testHandleFileMunge() {
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
// Ensure insecure uploads are disabled for this test.
$this->config('system.file')->set('allow_insecure_uploads', 0)->save();
$this->image = file_move($this->image, $this->image->getFileUri() . '.foo.' . $this->imageExtension);
// Reset the hook counters to get rid of the 'move' we just called.
file_test_reset();
$extensions = $this->imageExtension;
$edit = [
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'extensions' => $extensions,
];
$munged_filename = $this->image->getFilename();
$munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.'));
$munged_filename .= '_.' . $this->imageExtension;
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('For security reasons, your upload has been renamed'), 'Found security message.');
$this->assertRaw(t('File name is @filename', ['@filename' => $munged_filename]), 'File was successfully munged.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
// Ensure we don't munge files if we're allowing any extension.
// Reset the hook counters.
file_test_reset();
$edit = [
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'allow_all_extensions' => TRUE,
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found no security message.');
$this->assertRaw(t('File name is @filename', ['@filename' => $this->image->getFilename()]), 'File was not munged when allowing any extension.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
}
/**
* Tests renaming when uploading over a file that already exists.
*/
public function testExistingRename() {
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$edit = [
'file_test_replace' => FILE_EXISTS_RENAME,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
}
/**
* Tests replacement when uploading over a file that already exists.
*/
public function testExistingReplace() {
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'load', 'update']);
}
/**
* Tests for failure when uploading over a file that already exists.
*/
public function testExistingError() {
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$edit = [
'file_test_replace' => FILE_EXISTS_ERROR,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Check that the no hooks were called while failing.
$this->assertFileHooksCalled([]);
}
/**
* Tests for no failures when not uploading a file.
*/
public function testNoUpload() {
$this->drupalPostForm('file-test/save_upload_from_form_test', [], t('Submit'));
$this->assertNoRaw(t('Epic upload FAIL!'), 'Failure message not found.');
}
/**
* Tests for log entry on failing destination.
*/
public function testDrupalMovingUploadedFileError() {
// Create a directory and make it not writable.
$test_directory = 'test_drupal_move_uploaded_file_fail';
drupal_mkdir('temporary://' . $test_directory, 0000);
$this->assertTrue(is_dir('temporary://' . $test_directory));
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$edit = [
'file_subdir' => $test_directory,
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
];
\Drupal::state()->set('file_test.disable_error_collection', TRUE);
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('File upload error. Could not move uploaded file.'), 'Found the failure message.');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Uploading failed. Now check the log.
$this->drupalGet('admin/reports/dblog');
$this->assertResponse(200);
$this->assertRaw(t('Upload error. Could not move uploaded file @file to destination @destination.', [
'@file' => $this->image->getFilename(),
'@destination' => 'temporary://' . $test_directory . '/' . $this->image->getFilename(),
]), 'Found upload error log entry.');
}
/**
* Tests that form validation does not change error messages.
*/
public function testErrorMessagesAreNotChanged() {
$error = 'An error message set before _file_save_upload_from_form()';
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$edit = [
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'error_message' => $error,
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Ensure the expected error message is present and the counts before and
// after calling _file_save_upload_from_form() are correct.
$this->assertText($error);
$this->assertRaw('Number of error messages before _file_save_upload_from_form(): 1');
$this->assertRaw('Number of error messages after _file_save_upload_from_form(): 1');
// Test that error messages are preserved when an error occurs.
$edit = [
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'error_message' => $error,
'extensions' => 'foo',
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Ensure the expected error message is present and the counts before and
// after calling _file_save_upload_from_form() are correct.
$this->assertText($error);
$this->assertRaw('Number of error messages before _file_save_upload_from_form(): 1');
$this->assertRaw('Number of error messages after _file_save_upload_from_form(): 1');
// Test a successful upload with no messages.
$edit = [
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Ensure the error message is not present and the counts before and after
// calling _file_save_upload_from_form() are correct.
$this->assertNoText($error);
$this->assertRaw('Number of error messages before _file_save_upload_from_form(): 0');
$this->assertRaw('Number of error messages after _file_save_upload_from_form(): 0');
}
/**
* Tests that multiple validation errors are combined in one message.
*/
public function testCombinedErrorMessages() {
$textfile = current($this->drupalGetTestFiles('text'));
$this->assertTrue(is_file($textfile->uri), 'The text file we are going to upload exists.');
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
// Can't use drupalPostForm() for set nonexistent fields.
$this->drupalGet('file-test/save_upload_from_form_test');
$client = $this->getSession()->getDriver()->getClient();
$submit_xpath = $this->assertSession()->buttonExists('Submit')->getXpath();
$form = $client->getCrawler()->filterXPath($submit_xpath)->form();
$edit = [
'allow_all_extensions' => FALSE,
'is_image_file' => TRUE,
'extensions' => 'jpeg',
];
$edit += $form->getPhpValues();
$files['files']['file_test_upload'][0] = $file_system->realpath($this->phpfile->uri);
$files['files']['file_test_upload'][1] = $file_system->realpath($textfile->uri);
$client->request($form->getMethod(), $form->getUri(), $edit, $files);
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Search for combined error message followed by a formatted list of messages.
$this->assertRaw(t('One or more files could not be uploaded.') . '<div class="item-list">', 'Error message contains combined list of validation errors.');
}
/**
* Tests highlighting of file upload field when it has an error.
*/
public function testUploadFieldIsHighlighted() {
$this->assertEqual(0, count($this->cssSelect('input[name="files[file_test_upload][]"].error')), 'Successful file upload has no error.');
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$edit = [
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
'extensions' => 'foo',
];
$this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
$this->assertEqual(1, count($this->cssSelect('input[name="files[file_test_upload][]"].error')), 'File upload field has error.');
}
}

View file

@ -0,0 +1,368 @@
<?php
namespace Drupal\Tests\file\Functional;
use Drupal\file\Entity\File;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the file_save_upload() function.
*
* @group file
*/
class SaveUploadTest extends FileManagedTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['dblog'];
/**
* An image file path for uploading.
*
* @var \Drupal\file\FileInterface
*/
protected $image;
/**
* A PHP file path for upload security testing.
*/
protected $phpfile;
/**
* The largest file id when the test starts.
*/
protected $maxFidBefore;
/**
* Extension of the image filename.
*
* @var string
*/
protected $imageExtension;
protected function setUp() {
parent::setUp();
$account = $this->drupalCreateUser(['access site reports']);
$this->drupalLogin($account);
$image_files = $this->drupalGetTestFiles('image');
$this->image = File::create((array) current($image_files));
list(, $this->imageExtension) = explode('.', $this->image->getFilename());
$this->assertTrue(is_file($this->image->getFileUri()), "The image file we're going to upload exists.");
$this->phpfile = current($this->drupalGetTestFiles('php'));
$this->assertTrue(is_file($this->phpfile->uri), 'The PHP file we are going to upload exists.');
$this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
// Upload with replace to guarantee there's something there.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called then clean out the hook
// counters.
$this->assertFileHooksCalled(['validate', 'insert']);
file_test_reset();
}
/**
* Test the file_save_upload() function.
*/
public function testNormal() {
$max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
$this->assertTrue($max_fid_after > $this->maxFidBefore, 'A new file was created.');
$file1 = File::load($max_fid_after);
$this->assertTrue($file1, 'Loaded the file.');
// MIME type of the uploaded image may be either image/jpeg or image/png.
$this->assertEqual(substr($file1->getMimeType(), 0, 5), 'image', 'A MIME type was set.');
// Reset the hook counters to get rid of the 'load' we just called.
file_test_reset();
// Upload a second file.
$image2 = current($this->drupalGetTestFiles('image'));
$edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image2->uri)];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'));
$max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
$file2 = File::load($max_fid_after);
$this->assertTrue($file2, 'Loaded the file');
// MIME type of the uploaded image may be either image/jpeg or image/png.
$this->assertEqual(substr($file2->getMimeType(), 0, 5), 'image', 'A MIME type was set.');
// Load both files using File::loadMultiple().
$files = File::loadMultiple([$file1->id(), $file2->id()]);
$this->assertTrue(isset($files[$file1->id()]), 'File was loaded successfully');
$this->assertTrue(isset($files[$file2->id()]), 'File was loaded successfully');
// Upload a third file to a subdirectory.
$image3 = current($this->drupalGetTestFiles('image'));
$image3_realpath = \Drupal::service('file_system')->realpath($image3->uri);
$dir = $this->randomMachineName();
$edit = [
'files[file_test_upload]' => $image3_realpath,
'file_subdir' => $dir,
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'));
$this->assertTrue(is_file('temporary://' . $dir . '/' . trim(drupal_basename($image3_realpath))));
}
/**
* Test extension handling.
*/
public function testHandleExtension() {
// The file being tested is a .gif which is in the default safe list
// of extensions to allow when the extension validator isn't used. This is
// implicitly tested at the testNormal() test. Here we tell
// file_save_upload() to only allow ".foo".
$extensions = 'foo';
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
'extensions' => $extensions,
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$message = t('Only files with the following extensions are allowed:') . ' <em class="placeholder">' . $extensions . '</em>';
$this->assertRaw($message, 'Cannot upload a disallowed extension');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate']);
// Reset the hook counters.
file_test_reset();
$extensions = 'foo ' . $this->imageExtension;
// Now tell file_save_upload() to allow the extension of our test image.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
'extensions' => $extensions,
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('Only files with the following extensions are allowed:'), 'Can upload an allowed extension.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'load', 'update']);
// Reset the hook counters.
file_test_reset();
// Now tell file_save_upload() to allow any extension.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
'allow_all_extensions' => TRUE,
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('Only files with the following extensions are allowed:'), 'Can upload any extension.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'load', 'update']);
}
/**
* Test dangerous file handling.
*/
public function testHandleDangerousFile() {
$config = $this->config('system.file');
// Allow the .php extension and make sure it gets renamed to .txt for
// safety. Also check to make sure its MIME type was changed.
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->phpfile->uri),
'is_image_file' => FALSE,
'extensions' => 'php',
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$message = t('For security reasons, your upload has been renamed to') . ' <em class="placeholder">' . $this->phpfile->filename . '.txt' . '</em>';
$this->assertRaw($message, 'Dangerous file was renamed.');
$this->assertRaw(t('File MIME type is text/plain.'), "Dangerous file's MIME type was changed.");
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
// Ensure dangerous files are not renamed when insecure uploads is TRUE.
// Turn on insecure uploads.
$config->set('allow_insecure_uploads', 1)->save();
// Reset the hook counters.
file_test_reset();
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found no security message.');
$this->assertRaw(t('File name is @filename', ['@filename' => $this->phpfile->filename]), 'Dangerous file was not renamed when insecure uploads is TRUE.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
// Turn off insecure uploads.
$config->set('allow_insecure_uploads', 0)->save();
}
/**
* Test file munge handling.
*/
public function testHandleFileMunge() {
// Ensure insecure uploads are disabled for this test.
$this->config('system.file')->set('allow_insecure_uploads', 0)->save();
$this->image = file_move($this->image, $this->image->getFileUri() . '.foo.' . $this->imageExtension);
// Reset the hook counters to get rid of the 'move' we just called.
file_test_reset();
$extensions = $this->imageExtension;
$edit = [
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
'extensions' => $extensions,
];
$munged_filename = $this->image->getFilename();
$munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.'));
$munged_filename .= '_.' . $this->imageExtension;
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('For security reasons, your upload has been renamed'), 'Found security message.');
$this->assertRaw(t('File name is @filename', ['@filename' => $munged_filename]), 'File was successfully munged.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
// Ensure we don't munge files if we're allowing any extension.
// Reset the hook counters.
file_test_reset();
$edit = [
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
'allow_all_extensions' => TRUE,
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found no security message.');
$this->assertRaw(t('File name is @filename', ['@filename' => $this->image->getFilename()]), 'File was not munged when allowing any extension.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
}
/**
* Test renaming when uploading over a file that already exists.
*/
public function testExistingRename() {
$edit = [
'file_test_replace' => FILE_EXISTS_RENAME,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'insert']);
}
/**
* Test replacement when uploading over a file that already exists.
*/
public function testExistingReplace() {
$edit = [
'file_test_replace' => FILE_EXISTS_REPLACE,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('You WIN!'), 'Found the success message.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['validate', 'load', 'update']);
}
/**
* Test for failure when uploading over a file that already exists.
*/
public function testExistingError() {
$edit = [
'file_test_replace' => FILE_EXISTS_ERROR,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
];
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Check that the no hooks were called while failing.
$this->assertFileHooksCalled([]);
}
/**
* Test for no failures when not uploading a file.
*/
public function testNoUpload() {
$this->drupalPostForm('file-test/upload', [], t('Submit'));
$this->assertNoRaw(t('Epic upload FAIL!'), 'Failure message not found.');
}
/**
* Tests for log entry on failing destination.
*/
public function testDrupalMovingUploadedFileError() {
// Create a directory and make it not writable.
$test_directory = 'test_drupal_move_uploaded_file_fail';
drupal_mkdir('temporary://' . $test_directory, 0000);
$this->assertTrue(is_dir('temporary://' . $test_directory));
$edit = [
'file_subdir' => $test_directory,
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
];
\Drupal::state()->set('file_test.disable_error_collection', TRUE);
$this->drupalPostForm('file-test/upload', $edit, t('Submit'));
$this->assertResponse(200, 'Received a 200 response for posted test file.');
$this->assertRaw(t('File upload error. Could not move uploaded file.'), 'Found the failure message.');
$this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
// Uploading failed. Now check the log.
$this->drupalGet('admin/reports/dblog');
$this->assertResponse(200);
$this->assertRaw(t('Upload error. Could not move uploaded file @file to destination @destination.', [
'@file' => $this->image->getFilename(),
'@destination' => 'temporary://' . $test_directory . '/' . $this->image->getFilename(),
]), 'Found upload error log entry.');
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\Tests\file\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests File update path.
*
* @group legacy
*/
class FileUpdateTest extends UpdatePathTestBase {
/**
* Modules to enable after the database is loaded.
*/
protected static $modules = ['file'];
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.file_formatters_update_2677990.php',
];
}
/**
* Tests file_update_8001().
*/
public function testPostUpdate8001() {
$view = 'core.entity_view_display.node.article.default';
// Check that field_file_generic formatter has no
// use_description_as_link_text setting.
$formatter_settings = $this->config($view)->get('content.field_file_generic_2677990.settings');
$this->assertTrue(!isset($formatter_settings['use_description_as_link_text']));
// Check that field_file_table formatter has no use_description_as_link_text
// setting.
$formatter_settings = $this->config($view)->get('content.field_file_table_2677990.settings');
$this->assertTrue(!isset($formatter_settings['use_description_as_link_text']));
// Run updates.
$this->runUpdates();
// Check that field_file_generic formatter has a
// use_description_as_link_text setting which value is TRUE.
$formatter_settings = $this->config($view)->get('content.field_file_generic_2677990.settings');
$this->assertEqual($formatter_settings, ['use_description_as_link_text' => TRUE]);
// Check that field_file_table formatter has a use_description_as_link_text
// setting which value is FALSE.
$formatter_settings = $this->config($view)->get('content.field_file_table_2677990.settings');
$this->assertEqual($formatter_settings, ['use_description_as_link_text' => FALSE]);
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\Tests\file\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for setting the file usage deletion configuration.
*
* @see https://www.drupal.org/node/2801777
*
* @group Update
* @group legacy
*/
class FileUsageTemporaryDeletionConfigurationUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['file'];
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
];
}
/**
* Tests that make_unused_managed_files_temporary conditions are correct.
*
* Verify that the before and after conditions for the variable are correct.
*/
public function testUpdateHookN() {
$this->assertIdentical($this->config('file.settings')->get('make_unused_managed_files_temporary'), NULL);
$this->runUpdates();
$this->assertIdentical($this->config('file.settings')->get('make_unused_managed_files_temporary'), FALSE);
$this->assertResponse('200');
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\file\Functional\Views;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Views;
use Drupal\views\Tests\ViewTestData;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests file on user relationship handler.
*
* @group file
*/
class RelationshipUserFileDataTest extends ViewTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['file', 'file_test_views', 'user'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_file_user_file_data'];
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
// Create the user profile field and instance.
FieldStorageConfig::create([
'entity_type' => 'user',
'field_name' => 'user_file',
'type' => 'file',
'translatable' => '0',
])->save();
FieldConfig::create([
'label' => 'User File',
'description' => '',
'field_name' => 'user_file',
'entity_type' => 'user',
'bundle' => 'user',
'required' => 0,
])->save();
ViewTestData::createTestViews(get_class($this), ['file_test_views']);
}
/**
* Tests using the views file relationship.
*/
public function testViewsHandlerRelationshipUserFileData() {
$file = File::create([
'fid' => 2,
'uid' => 2,
'filename' => 'image-test.jpg',
'uri' => "public://image-test.jpg",
'filemime' => 'image/jpeg',
'created' => 1,
'changed' => 1,
'status' => FILE_STATUS_PERMANENT,
]);
$file->enforceIsNew();
file_put_contents($file->getFileUri(), file_get_contents('core/modules/simpletest/files/image-1.png'));
$file->save();
$account = $this->drupalCreateUser();
$account->user_file->target_id = 2;
$account->save();
$view = Views::getView('test_file_user_file_data');
// Tests \Drupal\taxonomy\Plugin\views\relationship\NodeTermData::calculateDependencies().
$expected = [
'module' => [
'file',
'user',
],
];
$this->assertIdentical($expected, $view->getDependencies());
$this->executeView($view);
$expected_result = [
[
'file_managed_user__user_file_fid' => '2',
],
];
$column_map = ['file_managed_user__user_file_fid' => 'file_managed_user__user_file_fid'];
$this->assertIdenticalResultset($view, $expected_result, $column_map);
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\Tests\file\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests validation functions such as file type, max file size, max size per
* node, and required.
*
* @group file
*/
class FileFieldValidateTest extends WebDriverTestBase {
use FileFieldCreationTrait;
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'file'];
/**
* Test the validation message is displayed only once for ajax uploads.
*/
public function testAjaxValidationMessage() {
$field_name = strtolower($this->randomMachineName());
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
$this->createFileField($field_name, 'node', 'article', [], ['file_extensions' => 'txt']);
$this->drupalLogin($this->drupalCreateUser([
'access content',
'create article content',
]));
$page = $this->getSession()->getPage();
$this->drupalGet('node/add/article');
$image_file = current($this->getTestFiles('image'));
$image_path = $this->container->get('file_system')->realpath($image_file->uri);
$page->attachFileToField('files[' . $field_name . '_0]', $image_path);
$elements = $page->waitFor(10, function () use ($page) {
return $page->findAll('css', '.messages--error');
});
$this->assertCount(1, $elements, 'Ajax validation messages are displayed once.');
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Drupal\Tests\file\FunctionalJavascript;
use Drupal\Core\Database\Database;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the 'managed_file' element type.
*
* @group file
*/
class FileManagedFileElementTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'file', 'file_module_test', 'field_ui'];
/**
* A user with administration permissions.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer node fields', 'administer node display', 'administer nodes', 'bypass node access']);
$this->drupalLogin($this->adminUser);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
}
/**
* Tests the managed_file element type.
*/
public function testManagedFile() {
// Perform the tests with all permutations of $form['#tree'],
// $element['#extended'], and $element['#multiple'].
$filename = \Drupal::service('file_system')->tempnam('temporary://', "testManagedFile") . '.txt';
file_put_contents($filename, $this->randomString(128));
foreach ([0, 1] as $tree) {
foreach ([0, 1] as $extended) {
foreach ([0, 1] as $multiple) {
$path = 'file/test/' . $tree . '/' . $extended . '/' . $multiple;
$input_base_name = $tree ? 'nested_file' : 'file';
$file_field_name = $multiple ? 'files[' . $input_base_name . '][]' : 'files[' . $input_base_name . ']';
// Now, test the Upload and Remove buttons, with Ajax.
// Upload, then Submit.
$last_fid_prior = $this->getLastFileId();
$this->drupalGet($path);
$this->getSession()->getPage()->attachFileToField($file_field_name, $this->container->get('file_system')->realpath($filename));
$uploaded_file = $this->assertSession()->waitForElement('css', '.file--mime-text-plain');
$this->assertNotEmpty($uploaded_file);
$last_fid = $this->getLastFileId();
$this->assertGreaterThan($last_fid_prior, $last_fid, 'New file got uploaded.');
$this->drupalPostForm(NULL, [], t('Save'));
// Remove, then Submit.
$remove_button_title = $multiple ? t('Remove selected') : t('Remove');
$this->drupalGet($path . '/' . $last_fid);
if ($multiple) {
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $last_fid . '][selected]';
$this->getSession()->getPage()->checkField($selected_checkbox);
}
$this->getSession()->getPage()->pressButton($remove_button_title);
$this->assertSession()->assertWaitOnAjaxRequest();
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertSession()->responseContains(t('The file ids are %fids.', ['%fids' => '']));
// Upload, then Remove, then Submit.
$this->drupalGet($path);
$this->getSession()->getPage()->attachFileToField($file_field_name, $this->container->get('file_system')->realpath($filename));
$uploaded_file = $this->assertSession()->waitForElement('css', '.file--mime-text-plain');
$this->assertNotEmpty($uploaded_file);
if ($multiple) {
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]';
$this->getSession()->getPage()->checkField($selected_checkbox);
}
$this->getSession()->getPage()->pressButton($remove_button_title);
$this->assertSession()->assertWaitOnAjaxRequest();
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertSession()->responseContains(t('The file ids are %fids.', ['%fids' => '']));
}
}
}
}
/**
* Retrieves the fid of the last inserted file.
*/
protected function getLastFileId() {
return (int) Database::getConnection()->query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Drupal\Tests\file\FunctionalJavascript;
use Drupal\Component\Utility\Bytes;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
/**
* Tests uploading a file that exceeds the maximum file size.
*
* @group file
*/
class MaximumFileSizeExceededUploadTest extends WebDriverTestBase {
use FileFieldCreationTrait;
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'file'];
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* A test user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* The original value of the 'display_errors' PHP configuration option.
*
* @todo Remove this when issue #2905597 is fixed.
* @see https://www.drupal.org/node/2905597
*
* @var string
*/
protected $originalDisplayErrorsValue;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->fileSystem = $this->container->get('file_system');
// Create the Article node type.
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Attach a file field to the node type.
$field_settings = ['file_extensions' => 'txt'];
$this->createFileField('field_file', 'node', 'article', [], $field_settings);
// Log in as a content author who can create Articles.
$this->user = $this->drupalCreateUser([
'access content',
'create article content',
]);
$this->drupalLogin($this->user);
// Disable the displaying of errors, so that the AJAX responses are not
// contaminated with error messages about exceeding the maximum POST size.
// @todo Remove this when issue #2905597 is fixed.
// @see https://www.drupal.org/node/2905597
$this->originalDisplayErrorsValue = ini_set('display_errors', '0');
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
// Restore the displaying of errors to the original value.
// @todo Remove this when issue #2905597 is fixed.
// @see https://www.drupal.org/node/2905597
ini_set('display_errors', $this->originalDisplayErrorsValue);
parent::tearDown();
}
/**
* Tests that uploading files exceeding maximum size are handled correctly.
*/
public function testUploadFileExceedingMaximumFileSize() {
$session = $this->getSession();
// Create a test file that exceeds the maximum POST size with 1 kilobyte.
$post_max_size = Bytes::toInt(ini_get('post_max_size'));
$invalid_file = $this->generateFile('exceeding_post_max_size', ceil(($post_max_size + 1024) / 1024), 1024);
// Go to the node creation form and try to upload the test file.
$this->drupalGet('node/add/article');
$page = $session->getPage();
$page->attachFileToField("files[field_file_0]", $this->fileSystem->realpath($invalid_file));
// An error message should appear informing the user that the file exceeded
// the maximum file size.
$this->assertSession()->waitForElement('css', '.messages--error');
// The error message includes the actual file size limit which depends on
// the current environment, so we check for a part of the message.
$this->assertSession()->pageTextContains('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size');
// Now upload a valid file and check that the error message disappears.
$valid_file = $this->generateFile('not_exceeding_post_max_size', 8, 8);
$page->attachFileToField("files[field_file_0]", $this->fileSystem->realpath($valid_file));
$this->assertSession()->waitForElement('named', ['id_or_name', 'field_file_0_remove_button']);
$this->assertSession()->elementNotExists('css', '.messages--error');
}
}

View file

@ -0,0 +1,150 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
/**
* Tests for the File access control.
*
* @group file
*/
class AccessTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['file', 'system', 'user'];
/**
* An authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $user1;
/**
* An authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $user2;
/**
* The file object used in the test.
*
* @var \Drupal\file\FileInterface
*/
protected $file;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('file');
$this->installEntitySchema('user');
$this->installSchema('file', ['file_usage']);
$this->installSchema('system', 'sequences');
$this->user1 = User::create([
'name' => 'user1',
'status' => 1,
]);
$this->user1->save();
$this->user2 = User::create([
'name' => 'user2',
'status' => 1,
]);
$this->user2->save();
$this->file = File::create([
'uid' => $this->user1->id(),
'filename' => 'druplicon.txt',
'filemime' => 'text/plain',
]);
}
/**
* Tests that only the file owner can delete or update a file.
*/
public function testOnlyOwnerCanDeleteUpdateFile() {
\Drupal::currentUser()->setAccount($this->user2);
$this->assertFalse($this->file->access('delete'));
$this->assertFalse($this->file->access('update'));
\Drupal::currentUser()->setAccount($this->user1);
$this->assertTrue($this->file->access('delete'));
$this->assertTrue($this->file->access('update'));
}
/**
* Tests file entity field access.
*
* @see \Drupal\file\FileAccessControlHandler::checkFieldAccess()
*/
public function testCheckFieldAccess() {
\Drupal::currentUser()->setAccount($this->user1);
/** @var \Drupal\file\FileInterface $file */
$file = File::create([
'uri' => 'public://test.png',
]);
// While creating a file entity access will be allowed for create-only
// fields.
$this->assertTrue($file->get('uri')->access('edit'));
$this->assertTrue($file->get('filemime')->access('edit'));
$this->assertTrue($file->get('filesize')->access('edit'));
// Access to the status field is denied whilst creating a file entity.
$this->assertFalse($file->get('status')->access('edit'));
$file->save();
// After saving the entity is no longer new and, therefore, access to
// create-only fields and the status field will be denied.
$this->assertFalse($file->get('uri')->access('edit'));
$this->assertFalse($file->get('filemime')->access('edit'));
$this->assertFalse($file->get('filesize')->access('edit'));
$this->assertFalse($file->get('status')->access('edit'));
}
/**
* Tests create access checks.
*/
public function testCreateAccess() {
// Anonymous users can create a file by default.
$this->assertFalse($this->file->access('create'));
// Authenticated users can create a file by default.
\Drupal::currentUser()->setAccount($this->user1);
$this->assertFalse($this->file->access('create'));
}
/**
* Tests cacheability metadata.
*/
public function testFileCacheability() {
$file = File::create([
'filename' => 'green-scarf',
'uri' => 'private://green-scarf',
'filemime' => 'text/plain',
'status' => FILE_STATUS_PERMANENT,
]);
$file->save();
\Drupal::service('session')->set('anonymous_allowed_file_ids', [$file->id() => $file->id()]);
$account = User::getAnonymousUser();
$file->setOwnerId($account->id())->save();
$this->assertSame(['session', 'user'], $file->access('view', $account, TRUE)->getCacheContexts());
$this->assertSame(['session', 'user'], $file->access('download', $account, TRUE)->getCacheContexts());
$account = $this->user1;
$file->setOwnerId($account->id())->save();
$this->assertSame(['user'], $file->access('view', $account, TRUE)->getCacheContexts());
$this->assertSame(['user'], $file->access('download', $account, TRUE)->getCacheContexts());
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\file\FileInterface;
use Drupal\file\ComputedFileUrl;
use Drupal\KernelTests\KernelTestBase;
/**
* @coversDefaultClass \Drupal\file\ComputedFileUrl
*
* @group file
*/
class ComputedFileUrlTest extends KernelTestBase {
/**
* The test URL to use.
*
* @var string
*/
protected $testUrl = 'public://druplicon.txt';
/**
* @covers ::getValue
*/
public function testGetValue() {
$entity = $this->prophesize(FileInterface::class);
$entity->getFileUri()
->willReturn($this->testUrl);
$parent = $this->prophesize(FieldItemInterface::class);
$parent->getEntity()
->shouldBeCalledTimes(2)
->willReturn($entity->reveal());
$definition = $this->prophesize(DataDefinitionInterface::class);
$typed_data = new ComputedFileUrl($definition->reveal(), $this->randomMachineName(), $parent->reveal());
$expected = base_path() . $this->siteDirectory . '/files/druplicon.txt';
$this->assertSame($expected, $typed_data->getValue());
// Do this a second time to confirm the same value is returned but the value
// isn't retrieved from the parent entity again.
$this->assertSame($expected, $typed_data->getValue());
}
/**
* @covers ::setValue
*/
public function testSetValue() {
$name = $this->randomMachineName();
$parent = $this->prophesize(FieldItemInterface::class);
$parent->onChange($name)
->shouldBeCalled();
$definition = $this->prophesize(DataDefinitionInterface::class);
$typed_data = new ComputedFileUrl($definition->reveal(), $name, $parent->reveal());
// Setting the value explicitly should mean the parent entity is never
// called into.
$typed_data->setValue($this->testUrl);
$this->assertSame($this->testUrl, $typed_data->getValue());
// Do this a second time to confirm the same value is returned but the value
// isn't retrieved from the parent entity again.
$this->assertSame($this->testUrl, $typed_data->getValue());
}
/**
* @covers ::setValue
*/
public function testSetValueNoNotify() {
$name = $this->randomMachineName();
$parent = $this->prophesize(FieldItemInterface::class);
$parent->onChange($name)
->shouldNotBeCalled();
$definition = $this->prophesize(DataDefinitionInterface::class);
$typed_data = new ComputedFileUrl($definition->reveal(), $name, $parent->reveal());
// Setting the value should explicitly should mean the parent entity is
// never called into.
$typed_data->setValue($this->testUrl, FALSE);
$this->assertSame($this->testUrl, $typed_data->getValue());
}
}

View file

@ -0,0 +1,146 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
/**
* Tests the file copy function.
*
* @group file
*/
class CopyTest extends FileManagedUnitTestBase {
/**
* Test file copying in the normal, base case.
*/
public function testNormal() {
$contents = $this->randomMachineName(10);
$source = $this->createFile(NULL, $contents);
$desired_uri = 'public://' . $this->randomMachineName();
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_copy(clone $source, $desired_uri, FILE_EXISTS_ERROR);
// Check the return status and that the contents changed.
$this->assertTrue($result, 'File copied successfully.');
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of file were copied correctly.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['copy', 'insert']);
$this->assertDifferentFile($source, $result);
$this->assertEqual($result->getFileUri(), $desired_uri, 'The copied file entity has the desired filepath.');
$this->assertTrue(file_exists($source->getFileUri()), 'The original file still exists.');
$this->assertTrue(file_exists($result->getFileUri()), 'The copied file exists.');
// Reload the file from the database and check that the changes were
// actually saved.
$this->assertFileUnchanged($result, File::load($result->id()));
}
/**
* Test renaming when copying over a file that already exists.
*/
public function testExistingRename() {
// Setup a file to overwrite.
$contents = $this->randomMachineName(10);
$source = $this->createFile(NULL, $contents);
$target = $this->createFile();
$this->assertDifferentFile($source, $target);
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_copy(clone $source, $target->getFileUri(), FILE_EXISTS_RENAME);
// Check the return status and that the contents changed.
$this->assertTrue($result, 'File copied successfully.');
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of file were copied correctly.');
$this->assertNotEqual($result->getFileUri(), $source->getFileUri(), 'Returned file path has changed from the original.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['copy', 'insert']);
// Load all the affected files to check the changes that actually made it
// to the database.
$loaded_source = File::load($source->id());
$loaded_target = File::load($target->id());
$loaded_result = File::load($result->id());
// Verify that the source file wasn't changed.
$this->assertFileUnchanged($source, $loaded_source);
// Verify that what was returned is what's in the database.
$this->assertFileUnchanged($result, $loaded_result);
// Make sure we end up with three distinct files afterwards.
$this->assertDifferentFile($loaded_source, $loaded_target);
$this->assertDifferentFile($loaded_target, $loaded_result);
$this->assertDifferentFile($loaded_source, $loaded_result);
}
/**
* Test replacement when copying over a file that already exists.
*/
public function testExistingReplace() {
// Setup a file to overwrite.
$contents = $this->randomMachineName(10);
$source = $this->createFile(NULL, $contents);
$target = $this->createFile();
$this->assertDifferentFile($source, $target);
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_copy(clone $source, $target->getFileUri(), FILE_EXISTS_REPLACE);
// Check the return status and that the contents changed.
$this->assertTrue($result, 'File copied successfully.');
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of file were overwritten.');
$this->assertDifferentFile($source, $result);
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['load', 'copy', 'update']);
// Load all the affected files to check the changes that actually made it
// to the database.
$loaded_source = File::load($source->id());
$loaded_target = File::load($target->id());
$loaded_result = File::load($result->id());
// Verify that the source file wasn't changed.
$this->assertFileUnchanged($source, $loaded_source);
// Verify that what was returned is what's in the database.
$this->assertFileUnchanged($result, $loaded_result);
// Target file was reused for the result.
$this->assertFileUnchanged($loaded_target, $loaded_result);
}
/**
* Test that copying over an existing file fails when FILE_EXISTS_ERROR is
* specified.
*/
public function testExistingError() {
$contents = $this->randomMachineName(10);
$source = $this->createFile();
$target = $this->createFile(NULL, $contents);
$this->assertDifferentFile($source, $target);
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_copy(clone $source, $target->getFileUri(), FILE_EXISTS_ERROR);
// Check the return status and that the contents were not changed.
$this->assertFalse($result, 'File copy failed.');
$this->assertEqual($contents, file_get_contents($target->getFileUri()), 'Contents of file were not altered.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled([]);
$this->assertFileUnchanged($source, File::load($source->id()));
$this->assertFileUnchanged($target, File::load($target->id()));
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
/**
* Tests the file delete function.
*
* @group file
*/
class DeleteTest extends FileManagedUnitTestBase {
/**
* Tries deleting a normal file (as opposed to a directory, symlink, etc).
*/
public function testUnused() {
$file = $this->createFile();
// Check that deletion removes the file and database record.
$this->assertTrue(is_file($file->getFileUri()), 'File exists.');
$file->delete();
$this->assertFileHooksCalled(['delete']);
$this->assertFalse(file_exists($file->getFileUri()), 'Test file has actually been deleted.');
$this->assertFalse(File::load($file->id()), 'File was removed from the database.');
}
/**
* Tries deleting a file that is in use.
*/
public function testInUse() {
// This test expects unused managed files to be marked as a temporary file
// and then deleted up by file_cron().
$this->config('file.settings')
->set('make_unused_managed_files_temporary', TRUE)
->save();
$file = $this->createFile();
$file_usage = $this->container->get('file.usage');
$file_usage->add($file, 'testing', 'test', 1);
$file_usage->add($file, 'testing', 'test', 1);
$file_usage->delete($file, 'testing', 'test', 1);
$usage = $file_usage->listUsage($file);
$this->assertEqual($usage['testing']['test'], [1 => 1], 'Test file is still in use.');
$this->assertTrue(file_exists($file->getFileUri()), 'File still exists on the disk.');
$this->assertTrue(File::load($file->id()), 'File still exists in the database.');
// Clear out the call to hook_file_load().
file_test_reset();
$file_usage->delete($file, 'testing', 'test', 1);
$usage = $file_usage->listUsage($file);
$this->assertFileHooksCalled(['load', 'update']);
$this->assertTrue(empty($usage), 'File usage data was removed.');
$this->assertTrue(file_exists($file->getFileUri()), 'File still exists on the disk.');
$file = File::load($file->id());
$this->assertTrue($file, 'File still exists in the database.');
$this->assertTrue($file->isTemporary(), 'File is temporary.');
file_test_reset();
// Call file_cron() to clean up the file. Make sure the changed timestamp
// of the file is older than the system.file.temporary_maximum_age
// configuration value.
db_update('file_managed')
->fields([
'changed' => REQUEST_TIME - ($this->config('system.file')->get('temporary_maximum_age') + 1),
])
->condition('fid', $file->id())
->execute();
\Drupal::service('cron')->run();
// file_cron() loads
$this->assertFileHooksCalled(['delete']);
$this->assertFalse(file_exists($file->getFileUri()), 'File has been deleted after its last usage was removed.');
$this->assertFalse(File::load($file->id()), 'File was removed from the database.');
}
}

View file

@ -0,0 +1,151 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\user\Entity\Role;
/**
* Tests using entity fields of the file field type.
*
* @group file
*/
class FileItemTest extends FieldKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['file'];
/**
* Created file entity.
*
* @var \Drupal\file\Entity\File
*/
protected $file;
/**
* Directory where the sample files are stored.
*
* @var string
*/
protected $directory;
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installConfig(['user']);
// Give anonymous users permission to access content, so they can view and
// download public files.
$anonymous_role = Role::load(Role::ANONYMOUS_ID);
$anonymous_role->grantPermission('access content');
$anonymous_role->save();
$this->installEntitySchema('file');
$this->installSchema('file', ['file_usage']);
FieldStorageConfig::create([
'field_name' => 'file_test',
'entity_type' => 'entity_test',
'type' => 'file',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
])->save();
$this->directory = $this->getRandomGenerator()->name(8);
FieldConfig::create([
'entity_type' => 'entity_test',
'field_name' => 'file_test',
'bundle' => 'entity_test',
'settings' => ['file_directory' => $this->directory],
])->save();
file_put_contents('public://example.txt', $this->randomMachineName());
$this->file = File::create([
'uri' => 'public://example.txt',
]);
$this->file->save();
}
/**
* Tests using entity fields of the file field type.
*/
public function testFileItem() {
// Check that the selection handler was automatically assigned to
// 'default:file'.
$field_definition = FieldConfig::load('entity_test.entity_test.file_test');
$handler_id = $field_definition->getSetting('handler');
$this->assertEqual($handler_id, 'default:file');
// Create a test entity with the
$entity = EntityTest::create();
$entity->file_test->target_id = $this->file->id();
$entity->file_test->display = 1;
$entity->file_test->description = $description = $this->randomMachineName();
$entity->name->value = $this->randomMachineName();
$entity->save();
$entity = EntityTest::load($entity->id());
$this->assertTrue($entity->file_test instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->file_test[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->file_test->target_id, $this->file->id());
$this->assertEqual($entity->file_test->display, 1);
$this->assertEqual($entity->file_test->description, $description);
$this->assertEqual($entity->file_test->entity->getFileUri(), $this->file->getFileUri());
$this->assertEqual($entity->file_test->entity->url(), $url = file_create_url($this->file->getFileUri()));
$this->assertEqual($entity->file_test->entity->id(), $this->file->id());
$this->assertEqual($entity->file_test->entity->uuid(), $this->file->uuid());
// Make sure the computed files reflects updates to the file.
file_put_contents('public://example-2.txt', $this->randomMachineName());
$file2 = File::create([
'uri' => 'public://example-2.txt',
]);
$file2->save();
$entity->file_test->target_id = $file2->id();
$this->assertEqual($entity->file_test->entity->id(), $file2->id());
$this->assertEqual($entity->file_test->entity->getFileUri(), $file2->getFileUri());
// Test the deletion of an entity having an entity reference field targeting
// a non-existing entity.
$file2->delete();
$entity->delete();
// Test the generateSampleValue() method.
$entity = EntityTest::create();
$entity->file_test->generateSampleItems();
$this->entityValidateAndSave($entity);
// Verify that the sample file was stored in the correct directory.
$uri = $entity->file_test->entity->getFileUri();
$this->assertEqual($this->directory, dirname(file_uri_target($uri)));
// Make sure the computed files reflects updates to the file.
file_put_contents('public://example-3.txt', $this->randomMachineName());
// Test unsaved file entity.
$file3 = File::create([
'uri' => 'public://example-3.txt',
]);
$display = entity_get_display('entity_test', 'entity_test', 'default');
$display->setComponent('file_test', [
'label' => 'above',
'type' => 'file_default',
'weight' => 1,
])->save();
$entity = EntityTest::create();
$entity->file_test = ['entity' => $file3];
$uri = $file3->getFileUri();
$output = entity_view($entity, 'default');
\Drupal::service('renderer')->renderRoot($output);
$this->assertTrue(!empty($entity->file_test->entity));
$this->assertEqual($entity->file_test->entity->getFileUri(), $uri);
}
}

View file

@ -0,0 +1,128 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
use org\bovigo\vfs\vfsStream;
/**
* Tests that files referenced in file and image fields are always validated.
*
* @group file
*/
class FileItemValidationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['file', 'image', 'entity_test', 'field', 'user', 'system'];
/**
* A user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installSchema('file', 'file_usage');
$this->installSchema('system', 'sequences');
$this->user = User::create([
'name' => 'username',
'status' => 1,
]);
$this->user->save();
$this->container->get('current_user')->setAccount($this->user);
}
/**
* @covers \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraint
* @covers \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidator
* @dataProvider getFileTypes
*/
public function testFileValidationConstraint($file_type) {
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_test_file',
'entity_type' => 'entity_test',
'type' => $file_type,
]);
$field_storage->save();
$field = FieldConfig::create([
'field_name' => 'field_test_file',
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'settings' => [
'max_filesize' => '2k',
'file_extensions' => 'jpg|png',
],
]);
$field->save();
vfsStream::setup('drupal_root');
vfsStream::create([
'sites' => [
'default' => [
'files' => [
'test.txt' => str_repeat('a', 3000),
],
],
],
]);
// Test for max filesize.
$file = File::create([
'uri' => 'vfs://drupal_root/sites/default/files/test.txt',
'uid' => $this->user->id(),
]);
$file->setPermanent();
$file->save();
$entity_test = EntityTest::create([
'uid' => $this->user->id(),
'field_test_file' => [
'target_id' => $file->id(),
],
]);
$result = $entity_test->validate();
$this->assertCount(2, $result);
$this->assertEquals('field_test_file.0', $result->get(0)->getPropertyPath());
$this->assertEquals('The file is <em class="placeholder">2.93 KB</em> exceeding the maximum file size of <em class="placeholder">2 KB</em>.', (string) $result->get(0)->getMessage());
$this->assertEquals('field_test_file.0', $result->get(1)->getPropertyPath());
$this->assertEquals('Only files with the following extensions are allowed: <em class="placeholder">jpg|png</em>.', (string) $result->get(1)->getMessage());
// Refer to a file that does not exist.
$entity_test = EntityTest::create([
'uid' => $this->user->id(),
'field_test_file' => [
'target_id' => 2,
],
]);
$result = $entity_test->validate();
$this->assertCount(1, $result);
$this->assertEquals('field_test_file.0.target_id', $result->get(0)->getPropertyPath());
$this->assertEquals('The referenced entity (<em class="placeholder">file</em>: <em class="placeholder">2</em>) does not exist.', (string) $result->get(0)->getMessage());
}
/**
* Provides a list of file types to test.
*/
public function getFileTypes() {
return [['file'], ['image']];
}
}

View file

@ -0,0 +1,213 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
/**
* Base class for file unit tests that use the file_test module to test uploads and
* hooks.
*/
abstract class FileManagedUnitTestBase extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['file_test', 'file', 'system', 'field', 'user'];
protected function setUp() {
parent::setUp();
// Clear out any hook calls.
file_test_reset();
$this->installConfig(['system']);
$this->installEntitySchema('file');
$this->installEntitySchema('user');
$this->installSchema('file', ['file_usage']);
// Make sure that a user with uid 1 exists, self::createFile() relies on
// it.
$user = User::create(['uid' => 1, 'name' => $this->randomMachineName()]);
$user->enforceIsNew();
$user->save();
\Drupal::currentUser()->setAccount($user);
}
/**
* Assert that all of the specified hook_file_* hooks were called once, other
* values result in failure.
*
* @param array $expected
* Array with string containing with the hook name, e.g. 'load', 'save',
* 'insert', etc.
*/
public function assertFileHooksCalled($expected) {
\Drupal::state()->resetCache();
// Determine which hooks were called.
$actual = array_keys(array_filter(file_test_get_all_calls()));
// Determine if there were any expected that were not called.
$uncalled = array_diff($expected, $actual);
if (count($uncalled)) {
$this->assertTrue(FALSE, format_string('Expected hooks %expected to be called but %uncalled was not called.', ['%expected' => implode(', ', $expected), '%uncalled' => implode(', ', $uncalled)]));
}
else {
$this->assertTrue(TRUE, format_string('All the expected hooks were called: %expected', ['%expected' => empty($expected) ? '(none)' : implode(', ', $expected)]));
}
// Determine if there were any unexpected calls.
$unexpected = array_diff($actual, $expected);
if (count($unexpected)) {
$this->assertTrue(FALSE, format_string('Unexpected hooks were called: %unexpected.', ['%unexpected' => empty($unexpected) ? '(none)' : implode(', ', $unexpected)]));
}
else {
$this->assertTrue(TRUE, 'No unexpected hooks were called.');
}
}
/**
* Assert that a hook_file_* hook was called a certain number of times.
*
* @param string $hook
* String with the hook name, e.g. 'load', 'save', 'insert', etc.
* @param int $expected_count
* Optional integer count.
* @param string $message
* Optional translated string message.
*/
public function assertFileHookCalled($hook, $expected_count = 1, $message = NULL) {
$actual_count = count(file_test_get_calls($hook));
if (!isset($message)) {
if ($actual_count == $expected_count) {
$message = format_string('hook_file_@name was called correctly.', ['@name' => $hook]);
}
elseif ($expected_count == 0) {
$message = \Drupal::translation()->formatPlural($actual_count, 'hook_file_@name was not expected to be called but was actually called once.', 'hook_file_@name was not expected to be called but was actually called @count times.', ['@name' => $hook, '@count' => $actual_count]);
}
else {
$message = format_string('hook_file_@name was expected to be called %expected times but was called %actual times.', ['@name' => $hook, '%expected' => $expected_count, '%actual' => $actual_count]);
}
}
$this->assertEqual($actual_count, $expected_count, $message);
}
/**
* Asserts that two files have the same values (except timestamp).
*
* @param \Drupal\file\FileInterface $before
* File object to compare.
* @param \Drupal\file\FileInterface $after
* File object to compare.
*/
public function assertFileUnchanged(FileInterface $before, FileInterface $after) {
$this->assertEqual($before->id(), $after->id(), t('File id is the same: %file1 == %file2.', ['%file1' => $before->id(), '%file2' => $after->id()]), 'File unchanged');
$this->assertEqual($before->getOwner()->id(), $after->getOwner()->id(), t('File owner is the same: %file1 == %file2.', ['%file1' => $before->getOwner()->id(), '%file2' => $after->getOwner()->id()]), 'File unchanged');
$this->assertEqual($before->getFilename(), $after->getFilename(), t('File name is the same: %file1 == %file2.', ['%file1' => $before->getFilename(), '%file2' => $after->getFilename()]), 'File unchanged');
$this->assertEqual($before->getFileUri(), $after->getFileUri(), t('File path is the same: %file1 == %file2.', ['%file1' => $before->getFileUri(), '%file2' => $after->getFileUri()]), 'File unchanged');
$this->assertEqual($before->getMimeType(), $after->getMimeType(), t('File MIME type is the same: %file1 == %file2.', ['%file1' => $before->getMimeType(), '%file2' => $after->getMimeType()]), 'File unchanged');
$this->assertEqual($before->getSize(), $after->getSize(), t('File size is the same: %file1 == %file2.', ['%file1' => $before->getSize(), '%file2' => $after->getSize()]), 'File unchanged');
$this->assertEqual($before->isPermanent(), $after->isPermanent(), t('File status is the same: %file1 == %file2.', ['%file1' => $before->isPermanent(), '%file2' => $after->isPermanent()]), 'File unchanged');
}
/**
* Asserts that two files are not the same by comparing the fid and filepath.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
*/
public function assertDifferentFile(FileInterface $file1, FileInterface $file2) {
$this->assertNotEqual($file1->id(), $file2->id(), t('Files have different ids: %file1 != %file2.', ['%file1' => $file1->id(), '%file2' => $file2->id()]), 'Different file');
$this->assertNotEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have different paths: %file1 != %file2.', ['%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri()]), 'Different file');
}
/**
* Asserts that two files are the same by comparing the fid and filepath.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
*/
public function assertSameFile(FileInterface $file1, FileInterface $file2) {
$this->assertEqual($file1->id(), $file2->id(), t('Files have the same ids: %file1 == %file2.', ['%file1' => $file1->id(), '%file2-fid' => $file2->id()]), 'Same file');
$this->assertEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have the same path: %file1 == %file2.', ['%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri()]), 'Same file');
}
/**
* Create a file and save it to the files table and assert that it occurs
* correctly.
*
* @param string $filepath
* Optional string specifying the file path. If none is provided then a
* randomly named file will be created in the site's files directory.
* @param string $contents
* Optional contents to save into the file. If a NULL value is provided an
* arbitrary string will be used.
* @param string $scheme
* Optional string indicating the stream scheme to use. Drupal core includes
* public, private, and temporary. The public wrapper is the default.
* @return \Drupal\file\FileInterface
* File entity.
*/
public function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
// Don't count hook invocations caused by creating the file.
\Drupal::state()->set('file_test.count_hook_invocations', FALSE);
$file = File::create([
'uri' => $this->createUri($filepath, $contents, $scheme),
'uid' => 1,
]);
$file->save();
// Write the record directly rather than using the API so we don't invoke
// the hooks.
$this->assertTrue($file->id() > 0, 'The file was added to the database.', 'Create test file');
\Drupal::state()->set('file_test.count_hook_invocations', TRUE);
return $file;
}
/**
* Creates a file and returns its URI.
*
* @param string $filepath
* Optional string specifying the file path. If none is provided then a
* randomly named file will be created in the site's files directory.
* @param string $contents
* Optional contents to save into the file. If a NULL value is provided an
* arbitrary string will be used.
* @param string $scheme
* Optional string indicating the stream scheme to use. Drupal core includes
* public, private, and temporary. The public wrapper is the default.
*
* @return string
* File URI.
*/
public function createUri($filepath = NULL, $contents = NULL, $scheme = NULL) {
if (!isset($filepath)) {
// Prefix with non-latin characters to ensure that all file-related
// tests work with international filenames.
$filepath = 'Файл для тестирования ' . $this->randomMachineName();
}
if (!isset($scheme)) {
$scheme = file_default_scheme();
}
$filepath = $scheme . '://' . $filepath;
if (!isset($contents)) {
$contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.";
}
file_put_contents($filepath, $contents);
$this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
return $filepath;
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
/**
* File URI field item test.
*
* @group file
*
* @see \Drupal\file\Plugin\Field\FieldType\FileUriItem
* @see \Drupal\file\FileUrl
*/
class FileUriItemTest extends FileManagedUnitTestBase {
/**
* Tests the file entity override of the URI field.
*/
public function testCustomFileUriField() {
$uri = 'public://druplicon.txt';
// Create a new file entity.
$file = File::create([
'uid' => 1,
'filename' => 'druplicon.txt',
'uri' => $uri,
'filemime' => 'text/plain',
'status' => FILE_STATUS_PERMANENT,
]);
file_put_contents($file->getFileUri(), 'hello world');
$file->save();
$this->assertSame($uri, $file->uri->value);
$expected_url = base_path() . $this->siteDirectory . '/files/druplicon.txt';
$this->assertSame($expected_url, $file->uri->url);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\Tests\file\Kernel;
/**
* Tests the file url.
*
* @group file
*/
class FileUrlTest extends FileManagedUnitTestBase {
/**
* Test public files with a different host name from settings.
*/
public function testFilesUrlWithDifferentHostName() {
$test_base_url = 'http://www.example.com/cdn';
$this->setSetting('file_public_base_url', $test_base_url);
$filepath = file_create_filename('test.txt', '');
$directory_uri = 'public://' . dirname($filepath);
file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY);
$file = $this->createFile($filepath, NULL, 'public');
$url = file_create_url($file->getFileUri());
$expected_url = $test_base_url . '/' . basename($filepath);
$this->assertSame($url, $expected_url);
}
}

View file

@ -0,0 +1,168 @@
<?php
namespace Drupal\Tests\file\Kernel\Formatter;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the default file formatter.
*
* @group field
*/
class FileEntityFormatterTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['file', 'user'];
/**
* The files.
*
* @var array
*/
protected $files;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('file');
$this->files = [];
file_put_contents('public://file.png', str_repeat('t', 10));
$file = File::create([
'uri' => 'public://file.png',
'filename' => 'file.png',
]);
$file->save();
$this->files[] = $file;
file_put_contents('public://file.tar', str_repeat('t', 200));
$file = File::create([
'uri' => 'public://file.tar',
'filename' => 'file.tar',
]);
$file->save();
$this->files[] = $file;
file_put_contents('public://file.tar.gz', str_repeat('t', 40000));
$file = File::create([
'uri' => 'public://file.tar.gz',
'filename' => 'file.tar.gz',
]);
$file->save();
$this->files[] = $file;
file_put_contents('public://file', str_repeat('t', 8000000));
$file = File::create([
'uri' => 'public://file',
'filename' => 'file',
]);
$file->save();
$this->files[] = $file;
}
/**
* Tests the file_link field formatter.
*/
public function testFormatterFileLink() {
$entity_display = EntityViewDisplay::create([
'targetEntityType' => 'file',
'bundle' => 'file',
]);
$entity_display->setComponent('filename', ['type' => 'file_link']);
$build = $entity_display->buildMultiple($this->files)[0]['filename'][0];
$this->assertEqual('file.png', $build['#title']);
$this->assertEqual(Url::fromUri(file_create_url('public://file.png')), $build['#url']);
}
/**
* Tests the file_link field formatter.
*/
public function testFormatterFileUri() {
$entity_display = EntityViewDisplay::create([
'targetEntityType' => 'file',
'bundle' => 'file',
]);
$entity_display->setComponent('uri', ['type' => 'file_uri']);
$build = $entity_display->buildMultiple($this->files)[0]['uri'][0];
$this->assertEqual('public://file.png', $build['#markup']);
$entity_display->setComponent('uri', ['type' => 'file_uri', 'settings' => ['file_download_path' => TRUE]]);
$build = $entity_display->buildMultiple($this->files)[0]['uri'][0];
$this->assertEqual(file_create_url('public://file.png'), $build['#markup']);
$entity_display->setComponent('uri', ['type' => 'file_uri', 'settings' => ['file_download_path' => TRUE, 'link_to_file' => TRUE]]);
$build = $entity_display->buildMultiple($this->files)[0]['uri'][0];
$this->assertEqual(file_create_url('public://file.png'), $build['#title']);
$this->assertEqual(Url::fromUri(file_create_url('public://file.png')), $build['#url']);
}
/**
* Tests the file_extension field formatter.
*/
public function testFormatterFileExtension() {
$entity_display = EntityViewDisplay::create([
'targetEntityType' => 'file',
'bundle' => 'file',
]);
$entity_display->setComponent('filename', ['type' => 'file_extension']);
$expected = ['png', 'tar', 'gz', ''];
foreach (array_values($this->files) as $i => $file) {
$build = $entity_display->build($file);
$this->assertEqual($expected[$i], $build['filename'][0]['#markup']);
}
$entity_display->setComponent('filename', ['type' => 'file_extension', 'settings' => ['extension_detect_tar' => TRUE]]);
$expected = ['png', 'tar', 'tar.gz', ''];
foreach (array_values($this->files) as $i => $file) {
$build = $entity_display->build($file);
$this->assertEqual($expected[$i], $build['filename'][0]['#markup']);
}
}
/**
* Tests the file_extension field formatter.
*/
public function testFormatterFileMime() {
$entity_display = EntityViewDisplay::create([
'targetEntityType' => 'file',
'bundle' => 'file',
]);
$entity_display->setComponent('filemime', ['type' => 'file_filemime', 'settings' => ['filemime_image' => TRUE]]);
foreach (array_values($this->files) as $i => $file) {
$build = $entity_display->build($file);
$this->assertEqual('image__file_icon', $build['filemime'][0]['#theme']);
$this->assertEqual(spl_object_hash($file), spl_object_hash($build['filemime'][0]['#file']));
}
}
/**
* Tests the file_size field formatter.
*/
public function testFormatterFileSize() {
$entity_display = EntityViewDisplay::create([
'targetEntityType' => 'file',
'bundle' => 'file',
]);
$entity_display->setComponent('filesize', ['type' => 'file_size']);
$expected = ['10 bytes', '200 bytes', '39.06 KB', '7.63 MB'];
foreach (array_values($this->files) as $i => $file) {
$build = $entity_display->build($file);
$this->assertEqual($expected[$i], $build['filesize'][0]['#markup']);
}
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
/**
* Tests \Drupal\file\Entity\File::load().
*
* @group file
*/
class LoadTest extends FileManagedUnitTestBase {
/**
* Try to load a non-existent file by fid.
*/
public function testLoadMissingFid() {
$this->assertFalse(File::load(-1), 'Try to load an invalid fid fails.');
$this->assertFileHooksCalled([]);
}
/**
* Try to load a non-existent file by URI.
*/
public function testLoadMissingFilepath() {
$files = entity_load_multiple_by_properties('file', ['uri' => 'foobar://misc/druplicon.png']);
$this->assertFalse(reset($files), "Try to load a file that doesn't exist in the database fails.");
$this->assertFileHooksCalled([]);
}
/**
* Try to load a non-existent file by status.
*/
public function testLoadInvalidStatus() {
$files = entity_load_multiple_by_properties('file', ['status' => -99]);
$this->assertFalse(reset($files), 'Trying to load a file with an invalid status fails.');
$this->assertFileHooksCalled([]);
}
/**
* Load a single file and ensure that the correct values are returned.
*/
public function testSingleValues() {
// Create a new file entity from scratch so we know the values.
$file = $this->createFile('druplicon.txt', NULL, 'public');
$by_fid_file = File::load($file->id());
$this->assertFileHookCalled('load');
$this->assertTrue(is_object($by_fid_file), '\Drupal\file\Entity\File::load() returned an object.');
$this->assertEqual($by_fid_file->id(), $file->id(), 'Loading by fid got the same fid.', 'File');
$this->assertEqual($by_fid_file->getFileUri(), $file->getFileUri(), 'Loading by fid got the correct filepath.', 'File');
$this->assertEqual($by_fid_file->getFilename(), $file->getFilename(), 'Loading by fid got the correct filename.', 'File');
$this->assertEqual($by_fid_file->getMimeType(), $file->getMimeType(), 'Loading by fid got the correct MIME type.', 'File');
$this->assertEqual($by_fid_file->isPermanent(), $file->isPermanent(), 'Loading by fid got the correct status.', 'File');
$this->assertTrue($by_fid_file->file_test['loaded'], 'file_test_file_load() was able to modify the file during load.');
}
/**
* This will test loading file data from the database.
*/
public function testMultiple() {
// Create a new file entity.
$file = $this->createFile('druplicon.txt', NULL, 'public');
// Load by path.
file_test_reset();
$by_path_files = entity_load_multiple_by_properties('file', ['uri' => $file->getFileUri()]);
$this->assertFileHookCalled('load');
$this->assertEqual(1, count($by_path_files), 'entity_load_multiple_by_properties() returned an array of the correct size.');
$by_path_file = reset($by_path_files);
$this->assertTrue($by_path_file->file_test['loaded'], 'file_test_file_load() was able to modify the file during load.');
$this->assertEqual($by_path_file->id(), $file->id(), 'Loading by filepath got the correct fid.', 'File');
// Load by fid.
file_test_reset();
$by_fid_files = File::loadMultiple([$file->id()]);
$this->assertFileHooksCalled([]);
$this->assertEqual(1, count($by_fid_files), '\Drupal\file\Entity\File::loadMultiple() returned an array of the correct size.');
$by_fid_file = reset($by_fid_files);
$this->assertTrue($by_fid_file->file_test['loaded'], 'file_test_file_load() was able to modify the file during load.');
$this->assertEqual($by_fid_file->getFileUri(), $file->getFileUri(), 'Loading by fid got the correct filepath.', 'File');
}
/**
* Loads a single file and ensure that the correct values are returned.
*/
public function testUuidValues() {
// Create a new file entity from scratch so we know the values.
$file = $this->createFile('druplicon.txt', NULL, 'public');
$file->save();
file_test_reset();
$by_uuid_file = \Drupal::entityManager()->loadEntityByUuid('file', $file->uuid());
$this->assertFileHookCalled('load');
$this->assertTrue(is_object($by_uuid_file), '\Drupal::entityManager()->loadEntityByUuid() returned a file object.');
if (is_object($by_uuid_file)) {
$this->assertEqual($by_uuid_file->id(), $file->id(), 'Loading by UUID got the same fid.', 'File');
$this->assertTrue($by_uuid_file->file_test['loaded'], 'file_test_file_load() was able to modify the file during load.');
}
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate;
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
use Drupal\migrate_drupal\Tests\StubTestTrait;
/**
* Test stub creation for file entities.
*
* @group file
*/
class MigrateFileStubTest extends MigrateDrupalTestBase {
use StubTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['file'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('file');
}
/**
* Tests creation of file stubs.
*/
public function testStub() {
$this->performStubTest('file');
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Helper for setting up a file migration test.
*/
trait FileMigrationTestTrait {
/**
* Setup and execute d6_file migration.
*/
protected function setUpMigratedFiles() {
$this->installEntitySchema('file');
$this->installConfig(['file']);
$this->executeMigration('d6_file');
}
/**
* {@inheritdoc}
*/
protected function prepareMigration(MigrationInterface $migration) {
// File migrations need a source_base_path.
// @see MigrateUpgradeRunBatch::run
$destination = $migration->getDestinationConfiguration();
if ($destination['plugin'] === 'entity:file') {
// Make sure we have a single trailing slash.
$source = $migration->getSourceConfiguration();
$source['site_path'] = 'core/modules/simpletest';
$source['constants']['source_base_path'] = $this->root . '/';
$migration->set('source', $source);
}
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade variables to file.settings.yml.
*
* @group migrate_drupal_6
*/
class MigrateFileConfigsTest extends MigrateDrupal6TestBase {
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('file_settings');
}
/**
* Tests migration of file variables to file.settings.yml.
*/
public function testFileSettings() {
$config = $this->config('file.settings');
$this->assertIdentical('textfield', $config->get('description.type'));
$this->assertIdentical(128, $config->get('description.length'));
$this->assertIdentical('sites/default/files/icons', $config->get('icon.directory'));
$this->assertConfigSchema(\Drupal::service('config.typed'), 'file.settings', $config->get());
}
}

View file

@ -0,0 +1,169 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Core\Database\Database;
use Drupal\Tests\migrate\Kernel\MigrateDumpAlterInterface;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Test file migration.
*
* @group migrate_drupal_6
*/
class MigrateFileTest extends MigrateDrupal6TestBase implements MigrateDumpAlterInterface {
use FileMigrationTestTrait;
/**
* The filename of a file used to test temporary file migration.
*
* @var string
*/
protected static $tempFilename;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->setUpMigratedFiles();
}
/**
* Asserts a file entity.
*
* @param int $fid
* The file ID.
* @param string $name
* The expected file name.
* @param int $size
* The expected file size.
* @param string $uri
* The expected file URI.
* @param string $type
* The expected MIME type.
* @param int $uid
* The expected file owner ID.
*/
protected function assertEntity($fid, $name, $size, $uri, $type, $uid) {
/** @var \Drupal\file\FileInterface $file */
$file = File::load($fid);
$this->assertTrue($file instanceof FileInterface);
$this->assertIdentical($name, $file->getFilename());
$this->assertIdentical($size, $file->getSize());
$this->assertIdentical($uri, $file->getFileUri());
$this->assertIdentical($type, $file->getMimeType());
$this->assertIdentical($uid, $file->getOwnerId());
}
/**
* Tests the Drupal 6 files to Drupal 8 migration.
*/
public function testFiles() {
$this->assertEntity(1, 'Image1.png', '39325', 'public://image-1.png', 'image/png', '1');
$this->assertEntity(2, 'Image2.jpg', '1831', 'public://image-2.jpg', 'image/jpeg', '1');
$this->assertEntity(3, 'image-3.jpg', '1831', 'public://image-3.jpg', 'image/jpeg', '1');
$this->assertEntity(4, 'html-1.txt', '24', 'public://html-1.txt', 'text/plain', '1');
// Ensure temporary file was not migrated.
$this->assertNull(File::load(6));
$map_table = $this->getMigration('d6_file')->getIdMap()->mapTableName();
$map = \Drupal::database()
->select($map_table, 'm')
->fields('m', ['sourceid1', 'destid1'])
->execute()
->fetchAllKeyed();
$map_expected = [
// The 4 files from the fixture.
1 => '1',
2 => '2',
// The file updated in migrateDumpAlter().
3 => '3',
5 => '4',
// The file created in migrateDumpAlter().
7 => '4',
];
$this->assertEquals($map_expected, $map);
// Test that we can re-import and also test with file_directory_path set.
\Drupal::database()
->truncate($map_table)
->execute();
// Update the file_directory_path.
Database::getConnection('default', 'migrate')
->update('variable')
->fields(['value' => serialize('files/test')])
->condition('name', 'file_directory_path')
->execute();
Database::getConnection('default', 'migrate')
->update('variable')
->fields(['value' => serialize(file_directory_temp())])
->condition('name', 'file_directory_temp')
->execute();
$this->executeMigration('d6_file');
// File 2, when migrated for the second time, is treated as a different file
// (due to having a different uri this time) and is given fid 6.
$file = File::load(6);
$this->assertIdentical('public://core/modules/simpletest/files/image-2.jpg', $file->getFileUri());
$map_table = $this->getMigration('d6_file')->getIdMap()->mapTableName();
$map = \Drupal::database()
->select($map_table, 'm')
->fields('m', ['sourceid1', 'destid1'])
->execute()
->fetchAllKeyed();
$map_expected = [
// The 4 files from the fixture.
1 => '5',
2 => '6',
// The file updated in migrateDumpAlter().
3 => '7',
5 => '8',
// The files created in migrateDumpAlter().
7 => '8',
8 => '8',
];
$this->assertEquals($map_expected, $map);
// File 6, created in static::migrateDumpAlter(), shares a path with
// file 4, which means it should be skipped entirely. If it was migrated
// then it would have an fid of 9.
$this->assertNull(File::load(9));
$this->assertEquals(8, count(File::loadMultiple()));
}
/**
* {@inheritdoc}
*/
public static function migrateDumpAlter(KernelTestBase $test) {
$db = Database::getConnection('default', 'migrate');
$db->update('files')
->condition('fid', 3)
->fields([
'filename' => 'image-3.jpg',
'filepath' => 'core/modules/simpletest/files/image-3.jpg',
])
->execute();
$file = (array) $db->select('files')
->fields('files')
->condition('fid', 5)
->execute()
->fetchObject();
unset($file['fid']);
$db->insert('files')->fields($file)->execute();
return static::$tempFilename;
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upload entity display.
*
* @group migrate_drupal_6
*/
class MigrateUploadEntityDisplayTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migrateFields();
}
/**
* Tests Drupal 6 upload settings to Drupal 8 entity display migration.
*/
public function testUploadEntityDisplay() {
$this->executeMigration('d6_upload_entity_display');
$display = EntityViewDisplay::load('node.page.default');
$component = $display->getComponent('upload');
$this->assertIdentical('file_default', $component['type']);
$display = EntityViewDisplay::load('node.story.default');
$component = $display->getComponent('upload');
$this->assertIdentical('file_default', $component['type']);
// Assure this doesn't exist.
$display = EntityViewDisplay::load('node.article.default');
$component = $display->getComponent('upload');
$this->assertTrue(is_null($component));
$this->assertIdentical(['node', 'page', 'default', 'upload'], $this->getMigration('d6_upload_entity_display')->getIdMap()->lookupDestinationId(['page']));
}
/**
* Tests that entity displays are ignored appropriately.
*
* Entity displays should be ignored when they belong to node types which
* were not migrated.
*/
public function testSkipNonExistentNodeType() {
// The "story" node type is migrated by d6_node_type but we need to pretend
// that it didn't occur, so record that in the map table.
$this->mockFailure('d6_node_type', ['type' => 'story']);
// d6_upload_entity_display should skip over the "story" node type config
// because, according to the map table, it didn't occur.
$migration = $this->getMigration('d6_upload_entity_display');
$this->executeMigration($migration);
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['node_type' => 'story'])[0][0]);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upload form entity display.
*
* @group migrate_drupal_6
*/
class MigrateUploadEntityFormDisplayTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migrateFields();
}
/**
* Tests Drupal 6 upload settings to Drupal 8 entity form display migration.
*/
public function testUploadEntityFormDisplay() {
$this->executeMigration('d6_upload_entity_form_display');
$display = EntityFormDisplay::load('node.page.default');
$component = $display->getComponent('upload');
$this->assertIdentical('file_generic', $component['type']);
$display = EntityFormDisplay::load('node.story.default');
$component = $display->getComponent('upload');
$this->assertIdentical('file_generic', $component['type']);
// Assure this doesn't exist.
$display = EntityFormDisplay::load('node.article.default');
$component = $display->getComponent('upload');
$this->assertTrue(is_null($component));
$this->assertIdentical(['node', 'page', 'default', 'upload'], $this->getMigration('d6_upload_entity_form_display')->getIdMap()->lookupDestinationId(['page']));
}
/**
* Tests that entity displays are ignored appropriately.
*
* Entity displays should be ignored when they belong to node types which
* were not migrated.
*/
public function testSkipNonExistentNodeType() {
// The "story" node type is migrated by d6_node_type but we need to pretend
// that it didn't occur, so record that in the map table.
$this->mockFailure('d6_node_type', ['type' => 'story']);
// d6_upload_entity_form_display should skip over the "story" node type
// config because according to the map table, it didn't occur.
$migration = $this->getMigration('d6_upload_entity_form_display');
$this->executeMigration($migration);
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['node_type' => 'story'])[0][0]);
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Uploads migration.
*
* @group migrate_drupal_6
*/
class MigrateUploadFieldTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migrateFields();
}
/**
* Tests the Drupal 6 upload settings to Drupal 8 field migration.
*/
public function testUpload() {
$field_storage = FieldStorageConfig::load('node.upload');
$this->assertIdentical('node.upload', $field_storage->id());
$this->assertIdentical(['node', 'upload'], $this->getMigration('d6_upload_field')->getIdMap()->lookupDestinationId(['']));
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upload field instance migration.
*
* @group migrate_drupal_6
*/
class MigrateUploadInstanceTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migrateFields();
}
/**
* Tests the Drupal 6 upload settings to Drupal 8 field instance migration.
*/
public function testUploadFieldInstance() {
$field = FieldConfig::load('node.page.upload');
$settings = $field->getSettings();
$this->assertIdentical('node.page.upload', $field->id());
$this->assertIdentical('jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp', $settings['file_extensions']);
$this->assertIdentical('1MB', $settings['max_filesize']);
$this->assertIdentical(TRUE, $settings['description_field']);
$field = FieldConfig::load('node.story.upload');
$this->assertIdentical('node.story.upload', $field->id());
// Shouldn't exist.
$field = FieldConfig::load('node.article.upload');
$this->assertTrue(is_null($field));
$this->assertIdentical(['node', 'page', 'upload'], $this->getMigration('d6_upload_field_instance')->getIdMap()->lookupDestinationId(['page']));
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d6;
use Drupal\file\Entity\File;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
use Drupal\node\Entity\Node;
/**
* Migrate association data between nodes and files.
*
* @group migrate_drupal_6
*/
class MigrateUploadTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('file');
$this->installEntitySchema('node');
$this->installSchema('file', ['file_usage']);
$this->installSchema('node', ['node_access']);
$id_mappings = ['d6_file' => []];
// Create new file entities.
for ($i = 1; $i <= 3; $i++) {
$file = File::create([
'fid' => $i,
'uid' => 1,
'filename' => 'druplicon.txt',
'uri' => "public://druplicon-$i.txt",
'filemime' => 'text/plain',
'created' => 1,
'changed' => 1,
'status' => FILE_STATUS_PERMANENT,
]);
$file->enforceIsNew();
file_put_contents($file->getFileUri(), 'hello world');
// Save it, inserting a new record.
$file->save();
$id_mappings['d6_file'][] = [[$i], [$i]];
}
$this->prepareMigrations($id_mappings);
$this->migrateContent(['translations']);
// Since we are only testing a subset of the file migration, do not check
// that the full file migration has been run.
$migration = $this->getMigration('d6_upload');
$migration->set('requirements', []);
$this->executeMigration($migration);
}
/**
* Test upload migration from Drupal 6 to Drupal 8.
*/
public function testUpload() {
$this->container->get('entity.manager')
->getStorage('node')
->resetCache([1, 2, 12]);
$nodes = Node::loadMultiple([1, 2, 12]);
$node = $nodes[1];
$this->assertEquals('en', $node->langcode->value);
$this->assertIdentical(1, count($node->upload));
$this->assertIdentical('1', $node->upload[0]->target_id);
$this->assertIdentical('file 1-1-1', $node->upload[0]->description);
$this->assertIdentical(FALSE, $node->upload[0]->isDisplayed());
$node = $nodes[2];
$this->assertEquals('en', $node->langcode->value);
$this->assertIdentical(2, count($node->upload));
$this->assertIdentical('3', $node->upload[0]->target_id);
$this->assertIdentical('file 2-3-3', $node->upload[0]->description);
$this->assertIdentical(FALSE, $node->upload[0]->isDisplayed());
$this->assertIdentical('2', $node->upload[1]->target_id);
$this->assertIdentical(TRUE, $node->upload[1]->isDisplayed());
$this->assertIdentical('file 2-3-2', $node->upload[1]->description);
$node = $nodes[12];
$this->assertEquals('zu', $node->langcode->value);
$this->assertEquals(1, count($node->upload));
$this->assertEquals('3', $node->upload[0]->target_id);
$this->assertEquals('file 12-15-3', $node->upload[0]->description);
$this->assertEquals(FALSE, $node->upload[0]->isDisplayed());
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d7;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
/**
* A trait to setup the file migration.
*/
trait FileMigrationSetupTrait {
/**
* Returns information about the file to be migrated.
*
* @return array
* Array with keys 'path', 'size', 'base_path', and 'plugin_id'.
*/
abstract protected function getFileMigrationInfo();
/**
* Prepare the file migration for running.
*/
protected function fileMigrationSetup() {
$this->installEntitySchema('file');
$this->installSchema('file', ['file_usage']);
$info = $this->getFileMigrationInfo();
$fs = $this->container->get('file_system');
// Ensure that the files directory exists.
$fs->mkdir(dirname($info['path']), NULL, TRUE);
// Put test file in the source directory.
file_put_contents($info['path'], str_repeat('*', $info['size']));
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->getMigration($info['plugin_id']);
// Set the source plugin's source_base_path configuration value, which
// would normally be set by the user running the migration.
$source = $migration->getSourceConfiguration();
$source['constants']['source_base_path'] = $fs->realpath($info['base_path']);
$migration->set('source', $source);
$this->executeMigration($migration);
}
/**
* Tests a single file entity.
*
* @param int $id
* The file ID.
* @param string $name
* The expected file name.
* @param string $uri
* The expected URI.
* @param string $mime
* The expected MIME type.
* @param string $size
* The expected file size.
* @param string $created
* The expected creation time.
* @param string $changed
* The expected modification time.
* @param string $uid
* The expected owner ID.
*/
protected function assertEntity($id, $name, $uri, $mime, $size, $created, $changed, $uid) {
/** @var \Drupal\file\FileInterface $file */
$file = File::load($id);
$this->assertInstanceOf(FileInterface::class, $file);
$this->assertSame($name, $file->getFilename());
$this->assertSame($uri, $file->getFileUri());
$this->assertFileExists($uri);
$this->assertSame($mime, $file->getMimeType());
$this->assertSame($size, $file->getSize());
// isPermanent(), isTemporary(), etc. are determined by the status column.
$this->assertTrue($file->isPermanent());
$this->assertSame($created, $file->getCreatedTime());
$this->assertSame($changed, $file->getChangedTime());
$this->assertSame($uid, $file->getOwnerId());
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d7;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Upgrade variables to file.settings.yml.
*
* @group migrate_drupal_7
*/
class MigrateFileConfigsTest extends MigrateDrupal7TestBase {
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('file_settings');
}
/**
* Tests migration of file variables to file.settings.yml.
*/
public function testFileSettings() {
$config = $this->config('file.settings');
$this->assertSame('textfield', $config->get('description.type'));
$this->assertSame(256, $config->get('description.length'));
$this->assertSame('sites/default/files/icons', $config->get('icon.directory'));
$this->assertConfigSchema(\Drupal::service('config.typed'), 'file.settings', $config->get());
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d7;
use Drupal\file\Entity\File;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Migrates all files in the file_managed table.
*
* @group file
*/
class MigrateFileTest extends MigrateDrupal7TestBase {
use FileMigrationSetupTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['file'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->fileMigrationSetup();
}
/**
* {@inheritdoc}
*/
protected function getFileMigrationInfo() {
return [
'path' => 'public://sites/default/files/cube.jpeg',
'size' => '3620',
'base_path' => 'public://',
'plugin_id' => 'd7_file',
];
}
/**
* Tests that all expected files are migrated.
*/
public function testFileMigration() {
$this->assertEntity(1, 'cube.jpeg', 'public://cube.jpeg', 'image/jpeg', '3620', '1421727515', '1421727515', '1');
// Ensure temporary file was not migrated.
$this->assertNull(File::load(4));
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\d7;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests private files migration.
*
* @group file
*/
class MigratePrivateFileTest extends MigrateDrupal7TestBase {
use FileMigrationSetupTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['file'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->setSetting('file_private_path', $this->container->get('site.path') . '/private');
$this->fileMigrationSetup();
}
/**
* {@inheritdoc}
*/
protected function getFileMigrationInfo() {
return [
'path' => 'private://sites/default/private/Babylon5.txt',
'size' => '3',
'base_path' => 'private://',
'plugin_id' => 'd7_file_private',
];
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$container->register('stream_wrapper.private', 'Drupal\Core\StreamWrapper\PrivateStream')
->addTag('stream_wrapper', ['scheme' => 'private']);
}
/**
* Tests that all expected files are migrated.
*/
public function testFileMigration() {
$this->assertEntity(3, 'Babylon5.txt', 'private://Babylon5.txt', 'text/plain', '3', '1486104045', '1486104045', '1');
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\file\Kernel\Migrate\process\d6;
use Drupal\file\Plugin\migrate\process\d6\CckFile;
use Drupal\migrate\Plugin\Migration;
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
/**
* Cck file field migration.
*
* @coversDefaultClass \Drupal\file\Plugin\migrate\process\d6\CckFile
*
* @group file
* @group legacy
*/
class CckFileTest extends MigrateDrupalTestBase {
/**
* Tests configurability of file migration name.
*
* @covers ::__construct
* @expectedDeprecation CckFile is deprecated in Drupal 8.3.x and will be be removed before Drupal 9.0.x. Use \Drupal\file\Plugin\migrate\process\d6\FieldFile instead.
*/
public function testConfigurableFileMigration() {
$migration = Migration::create($this->container, [], 'custom_migration', []);
$cck_file_migration = CckFile::create($this->container, ['migration' => 'custom_file'], 'custom_file', [], $migration);
$migration_plugin = $this->readAttribute($cck_file_migration, 'migrationPlugin');
$config = $this->readAttribute($migration_plugin, 'configuration');
$this->assertEquals($config['migration'], 'custom_file');
}
}

View file

@ -0,0 +1,161 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
/**
* Tests the file move function.
*
* @group file
*/
class MoveTest extends FileManagedUnitTestBase {
/**
* Move a normal file.
*/
public function testNormal() {
$contents = $this->randomMachineName(10);
$source = $this->createFile(NULL, $contents);
$desired_filepath = 'public://' . $this->randomMachineName();
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_move(clone $source, $desired_filepath, FILE_EXISTS_ERROR);
// Check the return status and that the contents changed.
$this->assertTrue($result, 'File moved successfully.');
$this->assertFalse(file_exists($source->getFileUri()));
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of file correctly written.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['move', 'load', 'update']);
// Make sure we got the same file back.
$this->assertEqual($source->id(), $result->id(), format_string("Source file id's' %fid is unchanged after move.", ['%fid' => $source->id()]));
// Reload the file from the database and check that the changes were
// actually saved.
$loaded_file = File::load($result->id());
$this->assertTrue($loaded_file, 'File can be loaded from the database.');
$this->assertFileUnchanged($result, $loaded_file);
}
/**
* Test renaming when moving onto a file that already exists.
*/
public function testExistingRename() {
// Setup a file to overwrite.
$contents = $this->randomMachineName(10);
$source = $this->createFile(NULL, $contents);
$target = $this->createFile();
$this->assertDifferentFile($source, $target);
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_move(clone $source, $target->getFileUri(), FILE_EXISTS_RENAME);
// Check the return status and that the contents changed.
$this->assertTrue($result, 'File moved successfully.');
$this->assertFalse(file_exists($source->getFileUri()));
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of file correctly written.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['move', 'load', 'update']);
// Compare the returned value to what made it into the database.
$this->assertFileUnchanged($result, File::load($result->id()));
// The target file should not have been altered.
$this->assertFileUnchanged($target, File::load($target->id()));
// Make sure we end up with two distinct files afterwards.
$this->assertDifferentFile($target, $result);
// Compare the source and results.
$loaded_source = File::load($source->id());
$this->assertEqual($loaded_source->id(), $result->id(), "Returned file's id matches the source.");
$this->assertNotEqual($loaded_source->getFileUri(), $source->getFileUri(), 'Returned file path has changed from the original.');
}
/**
* Test replacement when moving onto a file that already exists.
*/
public function testExistingReplace() {
// Setup a file to overwrite.
$contents = $this->randomMachineName(10);
$source = $this->createFile(NULL, $contents);
$target = $this->createFile();
$this->assertDifferentFile($source, $target);
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_move(clone $source, $target->getFileUri(), FILE_EXISTS_REPLACE);
// Look at the results.
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of file were overwritten.');
$this->assertFalse(file_exists($source->getFileUri()));
$this->assertTrue($result, 'File moved successfully.');
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['move', 'update', 'delete', 'load']);
// Reload the file from the database and check that the changes were
// actually saved.
$loaded_result = File::load($result->id());
$this->assertFileUnchanged($result, $loaded_result);
// Check that target was re-used.
$this->assertSameFile($target, $loaded_result);
// Source and result should be totally different.
$this->assertDifferentFile($source, $loaded_result);
}
/**
* Test replacement when moving onto itself.
*/
public function testExistingReplaceSelf() {
// Setup a file to overwrite.
$contents = $this->randomMachineName(10);
$source = $this->createFile(NULL, $contents);
// Copy the file over itself. Clone the object so we don't have to worry
// about the function changing our reference copy.
$result = file_move(clone $source, $source->getFileUri(), FILE_EXISTS_REPLACE);
$this->assertFalse($result, 'File move failed.');
$this->assertEqual($contents, file_get_contents($source->getFileUri()), 'Contents of file were not altered.');
// Check that no hooks were called while failing.
$this->assertFileHooksCalled([]);
// Load the file from the database and make sure it is identical to what
// was returned.
$this->assertFileUnchanged($source, File::load($source->id()));
}
/**
* Test that moving onto an existing file fails when FILE_EXISTS_ERROR is
* specified.
*/
public function testExistingError() {
$contents = $this->randomMachineName(10);
$source = $this->createFile();
$target = $this->createFile(NULL, $contents);
$this->assertDifferentFile($source, $target);
// Clone the object so we don't have to worry about the function changing
// our reference copy.
$result = file_move(clone $source, $target->getFileUri(), FILE_EXISTS_ERROR);
// Check the return status and that the contents did not change.
$this->assertFalse($result, 'File move failed.');
$this->assertTrue(file_exists($source->getFileUri()));
$this->assertEqual($contents, file_get_contents($target->getFileUri()), 'Contents of file were not altered.');
// Check that no hooks were called while failing.
$this->assertFileHooksCalled([]);
// Load the file from the database and make sure it is identical to what
// was returned.
$this->assertFileUnchanged($source, File::load($source->id()));
$this->assertFileUnchanged($target, File::load($target->id()));
}
}

View file

@ -0,0 +1,88 @@
<?php
namespace Drupal\Tests\file\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 file source plugin.
*
* @covers \Drupal\file\Plugin\migrate\source\d6\File
*
* @group file
*/
class FileTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['file', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['files'] = [
[
'fid' => 1,
'uid' => 1,
'filename' => 'migrate-test-file-1.pdf',
'filepath' => 'sites/default/files/migrate-test-file-1.pdf',
'filemime' => 'application/pdf',
'filesize' => 890404,
'status' => 1,
'timestamp' => 1382255613,
],
[
'fid' => 2,
'uid' => 1,
'filename' => 'migrate-test-file-2.pdf',
'filepath' => 'sites/default/files/migrate-test-file-2.pdf',
'filemime' => 'application/pdf',
'filesize' => 204124,
'status' => 1,
'timestamp' => 1382255662,
],
[
'fid' => 3,
'uid' => 1,
'filename' => 'migrate-test-file-3.pdf',
'filepath' => '/tmp/migrate-test-file-3.pdf',
'filemime' => 'application/pdf',
'filesize' => 304124,
'status' => 1,
'timestamp' => 1382277662,
],
];
// The expected results are the same as the source data but excluding
// the temporary file.
$tests[0]['expected_data'] = [
[
'fid' => 1,
'uid' => 1,
'filename' => 'migrate-test-file-1.pdf',
'filepath' => 'sites/default/files/migrate-test-file-1.pdf',
'filemime' => 'application/pdf',
'filesize' => 890404,
'status' => 1,
'timestamp' => 1382255613,
],
[
'fid' => 2,
'uid' => 1,
'filename' => 'migrate-test-file-2.pdf',
'filepath' => 'sites/default/files/migrate-test-file-2.pdf',
'filemime' => 'application/pdf',
'filesize' => 204124,
'status' => 1,
'timestamp' => 1382255662,
],
];
return $tests;
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Drupal\Tests\file\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 d6_upload_instance source plugin.
*
* @covers \Drupal\file\Plugin\migrate\source\d6\UploadInstance
*
* @group file
*/
class UploadInstanceTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['file', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['node_type'] = [
[
'type' => 'article',
],
[
'type' => 'company',
],
];
$tests[0]['source_data']['variable'] = [
[
'name' => 'upload_article',
'value' => serialize(TRUE),
],
[
'name' => 'upload_company',
'value' => serialize(FALSE),
],
[
'name' => 'upload_uploadsize_default',
'value' => serialize(16),
],
[
'name' => 'upload_extensions_default',
'value' => serialize('txt pdf'),
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'node_type' => 'article',
'max_filesize' => '16MB',
'file_extensions' => 'txt pdf',
],
];
return $tests;
}
}

View file

@ -0,0 +1,117 @@
<?php
namespace Drupal\Tests\file\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 d6_upload source plugin.
*
* @covers \Drupal\file\Plugin\migrate\source\d6\Upload
*
* @group file
*/
class UploadTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['file', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['upload'] = [
[
'fid' => '1',
'nid' => '1',
'vid' => '1',
'description' => 'file 1-1-1',
'list' => '0',
'weight' => '-1',
],
[
'fid' => '3',
'nid' => '12',
'vid' => '15',
'description' => 'file 12-15-3',
'list' => '0',
'weight' => '0',
],
];
$tests[0]['source_data']['node'] = [
[
'nid' => '1',
'vid' => '1',
'type' => 'story',
'language' => '',
'title' => 'Test title',
'uid' => '1',
'status' => '1',
'created' => '1388271197',
'changed' => '1420861423',
'comment' => '0',
'promote' => '0',
'moderate' => '0',
'sticky' => '0',
'tnid' => '0',
'translate' => '0',
],
[
'nid' => '12',
'vid' => '15',
'type' => 'page',
'language' => 'zu',
'title' => 'Abantu zulu',
'uid' => '1',
'status' => '1',
'created' => '1444238800',
'changed' => '1444238808',
'comment' => '0',
'promote' => '0',
'moderate' => '0',
'sticky' => '0',
'tnid' => '12',
'translate' => '0',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'upload' => [
[
'fid' => '1',
'description' => 'file 1-1-1',
'list' => '0',
],
],
'language' => '',
'nid' => '1',
'vid' => '1',
'type' => 'story',
],
[
'upload' => [
[
'fid' => '3',
'description' => 'file 12-15-3',
'list' => '0',
],
],
'language' => 'zu',
'nid' => '12',
'vid' => '15',
'type' => 'page',
],
];
return $tests;
}
}

View file

@ -0,0 +1,162 @@
<?php
namespace Drupal\Tests\file\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 file source plugin.
*
* @covers \Drupal\file\Plugin\migrate\source\d7\File
* @group file
*/
class FileTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['file', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0]['source_data']['file_managed'] = [
// A public file.
[
'fid' => '1',
'uid' => '1',
'filename' => 'cube.jpeg',
'uri' => 'public://cube.jpeg',
'filemime' => 'image/jpeg',
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
],
// A private file.
[
'fid' => '1',
'uid' => '1',
'filename' => 'cube.jpeg',
'uri' => 'private://cube.jpeg',
'filemime' => 'image/jpeg',
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
],
// A temporary file.
[
'fid' => '1',
'uid' => '1',
'filename' => 'cube.jpeg',
'uri' => 'temporary://cube.jpeg',
'filemime' => 'image/jpeg',
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
],
// A file with a URI scheme that will be filtered out.
[
'fid' => '1',
'uid' => '1',
'filename' => 'cube.jpeg',
'uri' => 'null://cube.jpeg',
'filemime' => 'image/jpeg',
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
],
];
$tests[0]['source_data']['variable'] = [
[
'name' => 'file_public_path',
'value' => serialize('sites/default/files'),
],
[
'name' => 'file_private_path',
'value' => serialize('/path/to/private/files'),
],
[
'name' => 'file_temporary_path',
'value' => serialize('/tmp'),
],
];
// The expected results will include only the first two files, since the
// plugin will filter out files with either the null URI scheme or the
// temporary scheme.
$tests[0]['expected_data'] = array_slice($tests[0]['source_data']['file_managed'], 0, 2);
// The filepath property will vary by URI scheme.
$tests[0]['expected_data'][0]['filepath'] = 'sites/default/files/cube.jpeg';
$tests[0]['expected_data'][1]['filepath'] = '/path/to/private/files/cube.jpeg';
// Do an automatic count.
$tests[0]['expected_count'] = NULL;
// Set up plugin configuration.
$tests[0]['configuration'] = [
'constants' => [
'source_base_path' => '/path/to/files',
],
'scheme' => ['public', 'private', 'temporary'],
];
// Test getting only public files.
$tests[1]['source_data'] = $tests[0]['source_data'];
$tests[1]['expected_data'] = [
[
'fid' => '1',
'uid' => '1',
'filename' => 'cube.jpeg',
'uri' => 'public://cube.jpeg',
'filemime' => 'image/jpeg',
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
],
];
// Do an automatic count.
$tests[1]['expected_count'] = NULL;
// Set up plugin configuration.
$tests[1]['configuration'] = [
'constants' => [
'source_base_path' => '/path/to/files',
],
'scheme' => ['public'],
];
// Test getting only public files when configuration scheme is not an array.
$tests[2]['source_data'] = $tests[0]['source_data'];
$tests[2]['expected_data'] = [
[
'fid' => '1',
'uid' => '1',
'filename' => 'cube.jpeg',
'uri' => 'public://cube.jpeg',
'filemime' => 'image/jpeg',
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
],
];
// Do an automatic count.
$tests[2]['expected_count'] = NULL;
// Set up plugin configuration.
$tests[2]['configuration'] = [
'constants' => [
'source_base_path' => '/path/to/files',
],
'scheme' => 'public',
];
return $tests;
}
}

View file

@ -0,0 +1,135 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\file\Entity\File;
/**
* Tests the file_save_data() function.
*
* @group file
*/
class SaveDataTest extends FileManagedUnitTestBase {
/**
* Test the file_save_data() function when no filename is provided.
*/
public function testWithoutFilename() {
$contents = $this->randomMachineName(8);
$result = file_save_data($contents);
$this->assertTrue($result, 'Unnamed file saved correctly.');
$this->assertEqual(file_default_scheme(), file_uri_scheme($result->getFileUri()), "File was placed in Drupal's files directory.");
$this->assertEqual($result->getFilename(), drupal_basename($result->getFileUri()), "Filename was set to the file's basename.");
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of the file are correct.');
$this->assertEqual($result->getMimeType(), 'application/octet-stream', 'A MIME type was set.');
$this->assertTrue($result->isPermanent(), "The file's status was set to permanent.");
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['insert']);
// Verify that what was returned is what's in the database.
$this->assertFileUnchanged($result, File::load($result->id()));
}
/**
* Test the file_save_data() function when a filename is provided.
*/
public function testWithFilename() {
$contents = $this->randomMachineName(8);
// Using filename with non-latin characters.
$filename = 'Текстовый файл.txt';
$result = file_save_data($contents, 'public://' . $filename);
$this->assertTrue($result, 'Unnamed file saved correctly.');
$this->assertEqual('public', file_uri_scheme($result->getFileUri()), "File was placed in Drupal's files directory.");
$this->assertEqual($filename, drupal_basename($result->getFileUri()), 'File was named correctly.');
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of the file are correct.');
$this->assertEqual($result->getMimeType(), 'text/plain', 'A MIME type was set.');
$this->assertTrue($result->isPermanent(), "The file's status was set to permanent.");
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['insert']);
// Verify that what was returned is what's in the database.
$this->assertFileUnchanged($result, File::load($result->id()));
}
/**
* Test file_save_data() when renaming around an existing file.
*/
public function testExistingRename() {
// Setup a file to overwrite.
$existing = $this->createFile();
$contents = $this->randomMachineName(8);
$result = file_save_data($contents, $existing->getFileUri(), FILE_EXISTS_RENAME);
$this->assertTrue($result, 'File saved successfully.');
$this->assertEqual('public', file_uri_scheme($result->getFileUri()), "File was placed in Drupal's files directory.");
$this->assertEqual($result->getFilename(), $existing->getFilename(), 'Filename was set to the basename of the source, rather than that of the renamed file.');
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of the file are correct.');
$this->assertEqual($result->getMimeType(), 'application/octet-stream', 'A MIME type was set.');
$this->assertTrue($result->isPermanent(), "The file's status was set to permanent.");
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['insert']);
// Ensure that the existing file wasn't overwritten.
$this->assertDifferentFile($existing, $result);
$this->assertFileUnchanged($existing, File::load($existing->id()));
// Verify that was returned is what's in the database.
$this->assertFileUnchanged($result, File::load($result->id()));
}
/**
* Test file_save_data() when replacing an existing file.
*/
public function testExistingReplace() {
// Setup a file to overwrite.
$existing = $this->createFile();
$contents = $this->randomMachineName(8);
$result = file_save_data($contents, $existing->getFileUri(), FILE_EXISTS_REPLACE);
$this->assertTrue($result, 'File saved successfully.');
$this->assertEqual('public', file_uri_scheme($result->getFileUri()), "File was placed in Drupal's files directory.");
$this->assertEqual($result->getFilename(), $existing->getFilename(), 'Filename was set to the basename of the existing file, rather than preserving the original name.');
$this->assertEqual($contents, file_get_contents($result->getFileUri()), 'Contents of the file are correct.');
$this->assertEqual($result->getMimeType(), 'application/octet-stream', 'A MIME type was set.');
$this->assertTrue($result->isPermanent(), "The file's status was set to permanent.");
// Check that the correct hooks were called.
$this->assertFileHooksCalled(['load', 'update']);
// Verify that the existing file was re-used.
$this->assertSameFile($existing, $result);
// Verify that what was returned is what's in the database.
$this->assertFileUnchanged($result, File::load($result->id()));
}
/**
* Test that file_save_data() fails overwriting an existing file.
*/
public function testExistingError() {
$contents = $this->randomMachineName(8);
$existing = $this->createFile(NULL, $contents);
// Check the overwrite error.
$result = file_save_data('asdf', $existing->getFileUri(), FILE_EXISTS_ERROR);
$this->assertFalse($result, 'Overwriting a file fails when FILE_EXISTS_ERROR is specified.');
$this->assertEqual($contents, file_get_contents($existing->getFileUri()), 'Contents of existing file were unchanged.');
// Check that no hooks were called while failing.
$this->assertFileHooksCalled([]);
// Ensure that the existing file wasn't overwritten.
$this->assertFileUnchanged($existing, File::load($existing->id()));
}
}

Some files were not shown because too many files have changed in this diff Show more