Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,4 @@
|
||||
name: 'File deprecation test'
|
||||
type: module
|
||||
description: 'Support module for testing deprecated file features.'
|
||||
package: Testing
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Support module for testing deprecated file features.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// cspell:ignore garply tarz
|
||||
|
||||
/**
|
||||
* Implements hook_file_mimetype_mapping_alter().
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Kept only for BC test coverage, see \Drupal\KernelTests\Core\File\MimeTypeLegacyTest.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3494040
|
||||
*/
|
||||
function file_deprecated_test_file_mimetype_mapping_alter(&$mapping): void {
|
||||
// Add new mappings.
|
||||
$mapping['mimetypes']['file_test_mimetype_1'] = 'made_up/file_test_1';
|
||||
$mapping['mimetypes']['file_test_mimetype_2'] = 'made_up/file_test_2';
|
||||
$mapping['mimetypes']['file_test_mimetype_3'] = 'made_up/doc';
|
||||
$mapping['mimetypes']['application-x-compress'] = 'application/x-compress';
|
||||
$mapping['mimetypes']['application-x-tarz'] = 'application/x-tarz';
|
||||
$mapping['mimetypes']['application-x-garply-waldo'] = 'application/x-garply-waldo';
|
||||
$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';
|
||||
$mapping['extensions']['z'] = 'application-x-compress';
|
||||
$mapping['extensions']['tar.z'] = 'application-x-tarz';
|
||||
$mapping['extensions']['garply.waldo'] = 'application-x-garply-waldo';
|
||||
// Override existing mapping.
|
||||
$mapping['extensions']['doc'] = 'file_test_mimetype_3';
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'File test'
|
||||
type: module
|
||||
description: 'Provides hooks for testing File module functionality.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -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'
|
||||
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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']),
|
||||
'#description' => $this->t('Upload a <em>@type</em> file', ['@type' => 'file & butter']),
|
||||
'#upload_location' => 'public://test',
|
||||
'#progress_message' => $this->t('Processing...'),
|
||||
'#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)]));
|
||||
}
|
||||
|
||||
}
|
||||
5
web/core/modules/file/tests/file_test/file_test.info.yml
Normal file
5
web/core/modules/file/tests/file_test/file_test.info.yml
Normal file
@ -0,0 +1,5 @@
|
||||
name: 'File test'
|
||||
type: module
|
||||
description: 'Support module for file handling tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
18
web/core/modules/file/tests/file_test/file_test.routing.yml
Normal file
18
web/core/modules/file/tests/file_test/file_test.routing.yml
Normal file
@ -0,0 +1,18 @@
|
||||
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'
|
||||
file.required_test:
|
||||
path: '/file-test/upload_required'
|
||||
defaults:
|
||||
_form: '\Drupal\file_test\Form\FileRequiredTestForm'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
26
web/core/modules/file/tests/file_test/file_test.services.yml
Normal file
26
web/core/modules/file/tests/file_test/file_test.services.yml
Normal file
@ -0,0 +1,26 @@
|
||||
services:
|
||||
_defaults:
|
||||
autoconfigure: true
|
||||
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 }
|
||||
stream_wrapper.dummy_external_readonly:
|
||||
class: Drupal\file_test\StreamWrapper\DummyExternalReadOnlyWrapper
|
||||
tags:
|
||||
- { name: stream_wrapper, scheme: dummy-external-readonly }
|
||||
stream_wrapper.dummy_multiple:
|
||||
class: Drupal\file_test\StreamWrapper\DummyMultipleStreamWrapper
|
||||
tags:
|
||||
- { name: stream_wrapper, scheme: dummy1 }
|
||||
- { name: stream_wrapper, scheme: dummy2 }
|
||||
Drupal\file_test\EventSubscriber\DummyMimeTypeMapLoadedSubscriber:
|
||||
autowire: true
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\EventSubscriber;
|
||||
|
||||
use Drupal\Core\File\Event\MimeTypeMapLoadedEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
// cspell:ignore garply tarz
|
||||
|
||||
/**
|
||||
* Modifies the MIME type map by adding dummy mappings.
|
||||
*/
|
||||
class DummyMimeTypeMapLoadedSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onMimeTypeMapLoaded(MimeTypeMapLoadedEvent $event): void {
|
||||
// Add new mappings.
|
||||
$event->map->addMapping('made_up/file_test_1', 'file_test_1');
|
||||
$event->map->addMapping('made_up/file_test_2', 'file_test_2');
|
||||
$event->map->addMapping('made_up/file_test_2', 'file_test_3');
|
||||
$event->map->addMapping('application/x-compress', 'z');
|
||||
$event->map->addMapping('application/x-tarz', 'tar.z');
|
||||
$event->map->addMapping('application/x-garply-waldo', 'garply.waldo');
|
||||
// Override existing mapping.
|
||||
$event->map->addMapping('made_up/doc', 'doc');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
return [
|
||||
MimeTypeMapLoadedEvent::class => 'onMimeTypeMapLoaded',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
22
web/core/modules/file/tests/file_test/src/FileTestCdn.php
Normal file
22
web/core/modules/file/tests/file_test/src/FileTestCdn.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test;
|
||||
|
||||
/**
|
||||
* Test Cdn.
|
||||
*/
|
||||
enum FileTestCdn: string {
|
||||
|
||||
/*
|
||||
* First Cdn.
|
||||
*/
|
||||
case First = 'http://cdn1.example.com';
|
||||
|
||||
/*
|
||||
* Second Cdn.
|
||||
*/
|
||||
case Second = 'http://cdn2.example.com';
|
||||
|
||||
}
|
||||
150
web/core/modules/file/tests/file_test/src/FileTestHelper.php
Normal file
150
web/core/modules/file/tests/file_test/src/FileTestHelper.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test;
|
||||
|
||||
/**
|
||||
* Helper for file tests.
|
||||
*
|
||||
* The caller must call reset() to initialize this module before
|
||||
* calling Drupal\file_test\FileTestHelper::getCalls() or setReturn().
|
||||
*/
|
||||
class FileTestHelper {
|
||||
|
||||
/**
|
||||
* Reset/initialize the history of calls to the file_* hooks.
|
||||
*
|
||||
* @see Drupal\file_test\FileTestHelper::getCalls()
|
||||
* @see Drupal\file_test\FileTestHelper::reset()
|
||||
*/
|
||||
public static function reset(): void {
|
||||
// Keep track of calls to these hooks
|
||||
$results = [
|
||||
'load' => [],
|
||||
'validate' => [],
|
||||
'download' => [],
|
||||
'insert' => [],
|
||||
'update' => [],
|
||||
'copy' => [],
|
||||
'move' => [],
|
||||
'delete' => [],
|
||||
];
|
||||
\Drupal::keyValue('file_test')->set('results', $results);
|
||||
|
||||
// These hooks will return these values, see FileTestHelper::setReturn().
|
||||
$return = [
|
||||
'validate' => [],
|
||||
'download' => NULL,
|
||||
];
|
||||
\Drupal::keyValue('file_test')->set('return', $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the arguments passed to a given hook invocation.
|
||||
*
|
||||
* Arguments are gathered since Drupal\file_test\FileTestHelper::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 Drupal\file_test\FileTestHelper::logCall()
|
||||
* @see Drupal\file_test\FileTestHelper::reset()
|
||||
*/
|
||||
public static function getCalls($op): array {
|
||||
$results = \Drupal::keyValue('file_test')->get('results', []);
|
||||
return $results[$op];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array with the calls for all hooks.
|
||||
*
|
||||
* @return array
|
||||
* An array keyed by hook name ('load', 'validate', 'download', 'insert',
|
||||
* 'update', 'copy', 'move', 'delete') with values being arrays of
|
||||
* parameters passed to each call.
|
||||
*/
|
||||
public static function getAllCalls(): array {
|
||||
return \Drupal::keyValue('file_test')->get('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 Drupal\file_test\FileTestHelper::getCalls()
|
||||
* @see Drupal\file_test\FileTestHelper::reset()
|
||||
*/
|
||||
public static function logCall($op, $args): void {
|
||||
if (\Drupal::state()->get('file_test.count_hook_invocations', TRUE)) {
|
||||
$results = \Drupal::keyValue('file_test')->get('results', []);
|
||||
$results[$op][] = $args;
|
||||
\Drupal::keyValue('file_test')->set('results', $results);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a return value for a given operation.
|
||||
*
|
||||
* @param string $op
|
||||
* One of the hook_file_[validate,download] operations.
|
||||
* @param array|int $value
|
||||
* Value for the hook to return.
|
||||
*
|
||||
* @see Drupal\file_test\FileTestHelper::getReturn()
|
||||
* @see Drupal\file_test\FileTestHelper::reset()
|
||||
*/
|
||||
public static function setReturn($op, $value): void {
|
||||
$return = \Drupal::keyValue('file_test')->get('return', []);
|
||||
|
||||
$return[$op] = $value;
|
||||
\Drupal::keyValue('file_test')->set('return', $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for testing FileSystemInterface::scanDirectory().
|
||||
*
|
||||
* 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.
|
||||
* @param bool $reset
|
||||
* (optional) If to reset the internal memory cache. If TRUE is passed, the
|
||||
* first parameter has no effect. Defaults to FALSE.
|
||||
*
|
||||
* @return array
|
||||
* If $filepath is NULL, an array of all previous $filepath parameters
|
||||
*/
|
||||
public static function fileScanCallback($filepath = NULL, $reset = FALSE): array {
|
||||
static $files = [];
|
||||
|
||||
if ($reset) {
|
||||
$files = [];
|
||||
}
|
||||
elseif ($filepath) {
|
||||
$files[] = $filepath;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset static variables used by FileTestHelper::fileScanCallback().
|
||||
*/
|
||||
public static function fileScanCallbackReset(): void {
|
||||
self::fileScanCallback(NULL, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* File required test form class.
|
||||
*/
|
||||
class FileRequiredTestForm extends FileTestForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId(): string {
|
||||
return '_file_required_test_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$form['file_test_upload']['#required'] = TRUE;
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
||||
104
web/core/modules/file/tests/file_test/src/Form/FileTestForm.php
Normal file
104
web/core/modules/file/tests/file_test/src/Form/FileTestForm.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\Form;
|
||||
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* File test form class.
|
||||
*/
|
||||
class FileTestForm extends FormBase {
|
||||
use FileTestFormTrait;
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId(): string {
|
||||
return '_file_test_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||
|
||||
$form = $this->baseForm($form, $form_state);
|
||||
|
||||
$form['file_test_upload'] = [
|
||||
'#type' => 'file',
|
||||
'#title' => $this->t('Upload a file'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||
// 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');
|
||||
\Drupal::service('file_system')->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY);
|
||||
}
|
||||
else {
|
||||
$destination = FALSE;
|
||||
}
|
||||
|
||||
// Setup validators.
|
||||
$validators = [];
|
||||
if ($form_state->getValue('is_image_file')) {
|
||||
$validators['FileIsImage'] = [];
|
||||
}
|
||||
|
||||
$allow = $form_state->getValue('allow_all_extensions');
|
||||
if ($allow === 'empty_array') {
|
||||
$validators['FileExtension'] = [];
|
||||
}
|
||||
elseif ($allow === 'empty_string') {
|
||||
$validators['FileExtension'] = ['extensions' => ''];
|
||||
}
|
||||
elseif (!$form_state->isValueEmpty('extensions')) {
|
||||
$validators['FileExtension'] = ['extensions' => $form_state->getValue('extensions')];
|
||||
}
|
||||
|
||||
// The test for \Drupal::service('file_system')->moveUploadedFile()
|
||||
// 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, static::fileExistsFromName($form_state->getValue('file_test_replace')));
|
||||
if ($file) {
|
||||
$form_state->setValue('file_test_upload', $file);
|
||||
\Drupal::messenger()->addStatus($this->t('File @filepath was uploaded.', ['@filepath' => $file->getFileUri()]));
|
||||
\Drupal::messenger()->addStatus($this->t('File name is @filename.', ['@filename' => $file->getFilename()]));
|
||||
\Drupal::messenger()->addStatus($this->t('File MIME type is @mimetype.', ['@mimetype' => $file->getMimeType()]));
|
||||
\Drupal::messenger()->addStatus($this->t('You WIN!'));
|
||||
}
|
||||
elseif ($file === FALSE) {
|
||||
\Drupal::messenger()->addError($this->t('Epic upload FAIL!'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a FileExists enum from its name.
|
||||
*/
|
||||
protected static function fileExistsFromName(string $name): FileExists {
|
||||
return match ($name) {
|
||||
FileExists::Replace->name => FileExists::Replace,
|
||||
FileExists::Error->name => FileExists::Error,
|
||||
default => FileExists::Rename,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\Form;
|
||||
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* This trait provides common code common for Forms.
|
||||
*/
|
||||
trait FileTestFormTrait {
|
||||
|
||||
/**
|
||||
* Adds common form elements to the form.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return array
|
||||
* The form structure.
|
||||
*/
|
||||
public function baseForm(array $form, FormStateInterface $form_state): array {
|
||||
|
||||
$form['file_test_replace'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('Replace existing image'),
|
||||
'#options' => [
|
||||
FileExists::Rename->name => new TranslatableMarkup('Appends number until name is unique'),
|
||||
FileExists::Replace->name => new TranslatableMarkup('Replace the existing file'),
|
||||
FileExists::Error->name => new TranslatableMarkup('Fail with an error'),
|
||||
],
|
||||
'#default_value' => FileExists::Rename->name,
|
||||
];
|
||||
$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'] = [
|
||||
'#title' => t('Allow all extensions?'),
|
||||
'#type' => 'radios',
|
||||
'#options' => [
|
||||
'false' => 'No',
|
||||
'empty_array' => 'Empty array',
|
||||
'empty_string' => 'Empty string',
|
||||
],
|
||||
'#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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\Form;
|
||||
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
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 {
|
||||
use FileTestFormTrait;
|
||||
|
||||
/**
|
||||
* 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 = $this->baseForm($form, $form_state);
|
||||
|
||||
$form['file_test_upload'] = [
|
||||
'#type' => 'file',
|
||||
'#multiple' => TRUE,
|
||||
'#title' => $this->t('Upload a file'),
|
||||
];
|
||||
|
||||
$form['error_message'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Custom error message.'),
|
||||
'#default_value' => '',
|
||||
];
|
||||
|
||||
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');
|
||||
\Drupal::service('file_system')->prepareDirectory($destination, FileSystemInterface::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['FileIsImage'] = [];
|
||||
}
|
||||
|
||||
$allow = $form_state->getValue('allow_all_extensions');
|
||||
if ($allow === 'empty_array') {
|
||||
$validators['FileExtension'] = [];
|
||||
}
|
||||
elseif ($allow === 'empty_string') {
|
||||
$validators['FileExtension'] = ['extensions' => ''];
|
||||
}
|
||||
elseif (!$form_state->isValueEmpty('extensions')) {
|
||||
$validators['FileExtension'] = ['extensions' => $form_state->getValue('extensions')];
|
||||
}
|
||||
|
||||
// The test for \Drupal::service('file_system')->moveUploadedFile()
|
||||
// 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, static::fileExistsFromName($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) {}
|
||||
|
||||
/**
|
||||
* Get a FileExists enum from its name.
|
||||
*/
|
||||
protected static function fileExistsFromName(string $name): FileExists {
|
||||
return match ($name) {
|
||||
FileExists::Replace->name => FileExists::Replace,
|
||||
FileExists::Error->name => FileExists::Error,
|
||||
default => FileExists::Rename,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
197
web/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php
Normal file
197
web/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php
Normal file
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\Hook;
|
||||
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file_test\FileTestCdn;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
|
||||
// cspell:ignore tarz
|
||||
// cspell:ignore garply
|
||||
|
||||
/**
|
||||
* Hook implementations for file_test.
|
||||
*/
|
||||
class FileTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_load() for file entities.
|
||||
*/
|
||||
#[Hook('file_load')]
|
||||
public function fileLoad($files): void {
|
||||
foreach ($files as $file) {
|
||||
FileTestHelper::logCall('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_download().
|
||||
*/
|
||||
#[Hook('file_download')]
|
||||
public function fileDownload($uri): array|int|null {
|
||||
if (\Drupal::state()->get('file_test.allow_all', FALSE)) {
|
||||
$files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $uri]);
|
||||
$file = reset($files);
|
||||
return $file->getDownloadHeaders();
|
||||
}
|
||||
FileTestHelper::logCall('download', [$uri]);
|
||||
return $this->getReturn('download');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_insert() for file entities.
|
||||
*/
|
||||
#[Hook('file_insert')]
|
||||
public function fileInsert(File $file): void {
|
||||
FileTestHelper::logCall('insert', [$file->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_update() for file entities.
|
||||
*/
|
||||
#[Hook('file_update')]
|
||||
public function fileUpdate(File $file): void {
|
||||
FileTestHelper::logCall('update', [$file->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_file_copy().
|
||||
*/
|
||||
#[Hook('file_copy')]
|
||||
public function fileCopy(File $file, $source): void {
|
||||
FileTestHelper::logCall('copy', [$file->id(), $source->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_file_move().
|
||||
*/
|
||||
#[Hook('file_move')]
|
||||
public function fileMove(File $file, File $source): void {
|
||||
FileTestHelper::logCall('move', [$file->id(), $source->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_predelete() for file entities.
|
||||
*/
|
||||
#[Hook('file_predelete')]
|
||||
public function filePredelete(File $file): void {
|
||||
FileTestHelper::logCall('delete', [$file->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_file_url_alter().
|
||||
*/
|
||||
#[Hook('file_url_alter')]
|
||||
public function fileUrlAlter(&$uri): void {
|
||||
/** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
|
||||
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
|
||||
// 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;
|
||||
}
|
||||
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 = $stream_wrapper_manager::getScheme($uri);
|
||||
// Only serve shipped files and public created files from the CDN.
|
||||
if (!$scheme || in_array($scheme, $schemes)) {
|
||||
// Shipped files.
|
||||
if (!$scheme) {
|
||||
$path = $uri;
|
||||
}
|
||||
else {
|
||||
$wrapper = $stream_wrapper_manager->getViaScheme($scheme);
|
||||
$path = $wrapper->getDirectoryPath() . '/' . $stream_wrapper_manager::getTarget($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 = FileTestCdn::First->value . '/' . $path;
|
||||
}
|
||||
else {
|
||||
$uri = FileTestCdn::Second->value . '/' . $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($alter_mode == 'root-relative') {
|
||||
// Only serve shipped files and public created files with root-relative
|
||||
// URLs.
|
||||
$scheme = $stream_wrapper_manager::getScheme($uri);
|
||||
if (!$scheme || $scheme == 'public') {
|
||||
// Shipped files.
|
||||
if (!$scheme) {
|
||||
$path = $uri;
|
||||
}
|
||||
else {
|
||||
$wrapper = $stream_wrapper_manager->getViaScheme($scheme);
|
||||
$path = $wrapper->getDirectoryPath() . '/' . $stream_wrapper_manager::getTarget($uri);
|
||||
}
|
||||
// Clean up Windows paths.
|
||||
$path = str_replace('\\', '/', $path);
|
||||
// Generate a root-relative URL.
|
||||
$uri = base_path() . '/' . $path;
|
||||
}
|
||||
}
|
||||
elseif ($alter_mode == 'protocol-relative') {
|
||||
// Only serve shipped files and public created files with
|
||||
// protocol-relative URLs.
|
||||
$scheme = $stream_wrapper_manager::getScheme($uri);
|
||||
if (!$scheme || $scheme == 'public') {
|
||||
// Shipped files.
|
||||
if (!$scheme) {
|
||||
$path = $uri;
|
||||
}
|
||||
else {
|
||||
$wrapper = $stream_wrapper_manager->getViaScheme($scheme);
|
||||
$path = $wrapper->getDirectoryPath() . '/' . $stream_wrapper_manager::getTarget($uri);
|
||||
}
|
||||
// Clean up Windows paths.
|
||||
$path = str_replace('\\', '/', $path);
|
||||
// Generate a protocol-relative URL.
|
||||
$uri = '/' . base_path() . '/' . $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
#[Hook('entity_type_alter')]
|
||||
public function entityTypeAlter(&$entity_types) : void {
|
||||
if (\Drupal::state()->get('file_test_alternate_access_handler', FALSE)) {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
|
||||
$entity_types['file']->setAccessClass('Drupal\file_test\FileTestAccessControlHandler');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the appropriate return value.
|
||||
*
|
||||
* @param string $op
|
||||
* One of the hook_file_[validate,download] operations.
|
||||
*
|
||||
* @return array|int|null
|
||||
* Value set by Drupal\file_test\FileTestHelper::setReturn().
|
||||
*
|
||||
* @see \Drupal\file_test\FileTestHelper::setReturn()
|
||||
* @see Drupal\file_test\FileTestHelper::reset()
|
||||
*/
|
||||
public function getReturn($op): array|int|null {
|
||||
$return = \Drupal::keyValue('file_test')->get('return', [$op => NULL]);
|
||||
return $return[$op];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\StreamWrapper;
|
||||
|
||||
use Drupal\Core\StreamWrapper\ReadOnlyStream;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
|
||||
|
||||
/**
|
||||
* Helper class for testing the stream wrapper registry.
|
||||
*
|
||||
* Dummy external stream wrapper implementation (dummy-external-readonly://).
|
||||
*/
|
||||
class DummyExternalReadOnlyWrapper extends ReadOnlyStream {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getType() {
|
||||
return StreamWrapperInterface::READ_VISIBLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return 'Dummy external stream wrapper (readonly)';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return 'Dummy external read-only stream wrapper for testing.';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExternalUrl() {
|
||||
[, $target] = explode('://', $this->uri, 2);
|
||||
return 'https://www.dummy-external-readonly.com/' . $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function realpath() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dirname($uri = NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dir_closedir() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dir_opendir($path, $options) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dir_readdir() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dir_rewinddir() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_cast($cast_as) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_close() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_eof() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_read($count) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_seek($offset, $whence = SEEK_SET) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_set_option($option, $arg1, $arg2) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_stat() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream_tell() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function url_stat($path, $flags) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test\StreamWrapper;
|
||||
|
||||
/**
|
||||
* Helper class for testing the stream wrapper registry.
|
||||
*
|
||||
* Dummy stream wrapper implementation (dummy1://, dummy2://).
|
||||
*/
|
||||
class DummyMultipleStreamWrapper extends DummyStreamWrapper {}
|
||||
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 'Dummy files (readonly)';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return 'Dummy wrapper for testing (readonly).';
|
||||
}
|
||||
|
||||
public function getDirectoryPath() {
|
||||
return \Drupal::getContainer()->getParameter('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';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 'Dummy remote read-only files';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return 'Dummy remote read-only stream wrapper for testing.';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 'Dummy files (remote)';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return 'Dummy wrapper for testing (remote).';
|
||||
}
|
||||
|
||||
public function realpath() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 'Dummy files';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return 'Dummy wrapper for testing.';
|
||||
}
|
||||
|
||||
public function getDirectoryPath() {
|
||||
return \Drupal::getContainer()->getParameter('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';
|
||||
}
|
||||
|
||||
}
|
||||
23
web/core/modules/file/tests/fixtures/update/file_post_update_playsinline-3046152.php
vendored
Normal file
23
web/core/modules/file/tests/fixtures/update/file_post_update_playsinline-3046152.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Fixture file to test file_post_update_add_playsinline().
|
||||
*
|
||||
* @see https://www.drupal.org/project/drupal/issues/3046152
|
||||
* @see \Drupal\Tests\file\Functional\Formatter\FileVideoFormatterUpdateTest
|
||||
* @see \file_post_update_add_playsinline()
|
||||
*/
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$display = Yaml::decode(file_get_contents(__DIR__ . '/post_update_playsinline-3046152-node-article.yml'));
|
||||
|
||||
$db = Database::getConnection();
|
||||
$db->update('config')
|
||||
->fields([
|
||||
'data' => serialize($display),
|
||||
])
|
||||
->condition('name', 'core.entity_view_display.node.article.default')
|
||||
->execute();
|
||||
79
web/core/modules/file/tests/fixtures/update/post_update_playsinline-3046152-node-article.yml
vendored
Normal file
79
web/core/modules/file/tests/fixtures/update/post_update_playsinline-3046152-node-article.yml
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
uuid: 93ccb838-5462-45ee-862a-988b9bdc6e0f
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_display.comment.comment.default
|
||||
- field.field.node.article.body
|
||||
- field.field.node.article.comment
|
||||
- field.field.node.article.field_image
|
||||
- field.field.node.article.field_tags
|
||||
- field.field.node.article.field_video
|
||||
- image.style.wide
|
||||
- node.type.article
|
||||
module:
|
||||
- comment
|
||||
- file
|
||||
- image
|
||||
- text
|
||||
- user
|
||||
id: node.article.default
|
||||
targetEntityType: node
|
||||
bundle: article
|
||||
mode: default
|
||||
content:
|
||||
body:
|
||||
type: text_default
|
||||
label: hidden
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
weight: 0
|
||||
region: content
|
||||
comment:
|
||||
type: comment_default
|
||||
label: above
|
||||
settings:
|
||||
view_mode: default
|
||||
pager_id: 0
|
||||
third_party_settings: { }
|
||||
weight: 110
|
||||
region: content
|
||||
field_image:
|
||||
type: image
|
||||
label: hidden
|
||||
settings:
|
||||
image_link: ''
|
||||
image_style: wide
|
||||
image_loading:
|
||||
attribute: eager
|
||||
third_party_settings: { }
|
||||
weight: -1
|
||||
region: content
|
||||
field_tags:
|
||||
type: entity_reference_label
|
||||
label: above
|
||||
settings:
|
||||
link: true
|
||||
third_party_settings: { }
|
||||
weight: 10
|
||||
region: content
|
||||
field_video:
|
||||
type: file_video
|
||||
label: above
|
||||
settings:
|
||||
controls: true
|
||||
autoplay: false
|
||||
loop: false
|
||||
multiple_file_display_type: tags
|
||||
muted: true
|
||||
width: 640
|
||||
height: 480
|
||||
third_party_settings: { }
|
||||
weight: 111
|
||||
region: content
|
||||
links:
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
weight: 100
|
||||
region: content
|
||||
hidden: { }
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'File test getIds'
|
||||
type: module
|
||||
description: 'Provides file source plugin restricted to used files.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:file
|
||||
@ -0,0 +1,33 @@
|
||||
id: d7_file_used
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
source:
|
||||
plugin: d7_file_used
|
||||
scheme: public
|
||||
constants:
|
||||
source_base_path: ''
|
||||
process:
|
||||
fid: fid
|
||||
filename: filename
|
||||
source_full_path:
|
||||
-
|
||||
plugin: concat
|
||||
delimiter: /
|
||||
source:
|
||||
- constants/source_base_path
|
||||
- filepath
|
||||
-
|
||||
plugin: urlencode
|
||||
uri:
|
||||
plugin: file_copy
|
||||
source:
|
||||
- '@source_full_path'
|
||||
- uri
|
||||
filemime: filemime
|
||||
status: status
|
||||
created: timestamp
|
||||
changed: timestamp
|
||||
uid: uid
|
||||
destination:
|
||||
plugin: entity:file
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_test_get_ids\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\file\Plugin\migrate\source\d7\File;
|
||||
|
||||
/**
|
||||
* Drupal 7 file source from database restricted to used files.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_file_used",
|
||||
* source_module = "file"
|
||||
* )
|
||||
*/
|
||||
class FileUsed extends File {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = parent::query();
|
||||
|
||||
// Join on file_usage table to only migrate used files.
|
||||
$query->innerJoin('file_usage', 'fu', 'f.fid = fu.fid');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
type: file_test
|
||||
name: File test
|
||||
description: 'Bundle for testing file relationships.'
|
||||
help: null
|
||||
new_revision: true
|
||||
display_submitted: true
|
||||
preview_mode: 1
|
||||
status: true
|
||||
langcode: en
|
||||
@ -0,0 +1,217 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- node
|
||||
- user
|
||||
id: test_file_to_node
|
||||
label: test_file_to_node
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: file_managed
|
||||
base_field: fid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
filename:
|
||||
id: filename
|
||||
table: file_managed
|
||||
field: filename
|
||||
entity_type: file
|
||||
entity_field: filename
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
plugin_id: field
|
||||
type: file_link
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: file_to_node_1
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: field
|
||||
filters: { }
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships:
|
||||
file_to_node_1:
|
||||
id: file_to_node_1
|
||||
table: file_usage
|
||||
field: file_to_node
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: Content
|
||||
required: true
|
||||
plugin_id: standard
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -0,0 +1,214 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- user
|
||||
id: test_file_to_user
|
||||
label: test_file_to_user
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: file_managed
|
||||
base_field: fid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
filename:
|
||||
id: filename
|
||||
table: file_managed
|
||||
field: filename
|
||||
entity_type: file
|
||||
entity_field: filename
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
plugin_id: field
|
||||
type: file_link
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
uuid:
|
||||
id: uuid
|
||||
table: users
|
||||
field: uuid
|
||||
relationship: file_to_user
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: uuid
|
||||
plugin_id: field
|
||||
filters: { }
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships:
|
||||
file_to_user:
|
||||
id: file_to_user
|
||||
table: file_usage
|
||||
field: file_to_user
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: User
|
||||
required: true
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -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
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access user profiles'
|
||||
cache:
|
||||
type: tag
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
class: ''
|
||||
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: { }
|
||||
@ -0,0 +1,219 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- node
|
||||
- user
|
||||
id: test_node_to_file
|
||||
label: test_node_to_file
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
fid:
|
||||
id: fid
|
||||
table: file_managed
|
||||
field: fid
|
||||
relationship: node_to_file
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: file
|
||||
entity_field: fid
|
||||
plugin_id: field
|
||||
filters: { }
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships:
|
||||
node_to_file:
|
||||
id: node_to_file
|
||||
table: file_usage
|
||||
field: node_to_file
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: File
|
||||
required: true
|
||||
plugin_id: standard
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -0,0 +1,233 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- user
|
||||
id: test_user_to_file
|
||||
label: test_user_to_file
|
||||
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
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
fid:
|
||||
id: fid
|
||||
table: file_managed
|
||||
field: fid
|
||||
relationship: user_to_file
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: file
|
||||
entity_field: fid
|
||||
plugin_id: field
|
||||
uuid:
|
||||
id: uuid
|
||||
table: users
|
||||
field: uuid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: uuid
|
||||
plugin_id: field
|
||||
filters: { }
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships:
|
||||
user_to_file:
|
||||
id: user_to_file
|
||||
table: file_usage
|
||||
field: user_to_file
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: File
|
||||
required: true
|
||||
plugin_id: standard
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'File test views'
|
||||
type: module
|
||||
description: 'Provides default views for views file tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:file
|
||||
- drupal:views
|
||||
@ -0,0 +1,47 @@
|
||||
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
|
||||
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: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,7 @@
|
||||
name: File validator test
|
||||
type: module
|
||||
description: Provides classes for file validator tests
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:file
|
||||
@ -0,0 +1,7 @@
|
||||
services:
|
||||
_defaults:
|
||||
autoconfigure: true
|
||||
file_validation_test_subscriber:
|
||||
class: Drupal\file_validator_test\EventSubscriber\FileValidationTestSubscriber
|
||||
file_validation_sanitization_subscriber:
|
||||
class: Drupal\file_validator_test\EventSubscriber\FileSanitizationEventSubscriber
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_validator_test\EventSubscriber;
|
||||
|
||||
use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Provides a file sanitization listener for file upload tests.
|
||||
*/
|
||||
class FileSanitizationEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The allowed extensions.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $allowedExtensions = [];
|
||||
|
||||
/**
|
||||
* Handles the file sanitization event.
|
||||
*/
|
||||
public function onFileSanitization(FileUploadSanitizeNameEvent $event) {
|
||||
$this->allowedExtensions = $event->getAllowedExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the allowed extensions.
|
||||
*
|
||||
* @return string[]
|
||||
* The allowed extensions.
|
||||
*/
|
||||
public function getAllowedExtensions(): array {
|
||||
return $this->allowedExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
return [FileUploadSanitizeNameEvent::class => 'onFileSanitization'];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\file_validator_test\EventSubscriber;
|
||||
|
||||
use Drupal\file\Validation\FileValidationEvent;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Provides a validation listener for file validation tests.
|
||||
*/
|
||||
class FileValidationTestSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Handles the file validation event.
|
||||
*
|
||||
* @param \Drupal\file\Validation\FileValidationEvent $event
|
||||
* The event.
|
||||
*/
|
||||
public function onFileValidation(FileValidationEvent $event): void {
|
||||
FileTestHelper::logCall('validate', [$event->file->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
return [FileValidationEvent::class => 'onFileValidation'];
|
||||
}
|
||||
|
||||
}
|
||||
225
web/core/modules/file/tests/src/Functional/DownloadTest.php
Normal file
225
web/core/modules/file/tests/src/Functional/DownloadTest.php
Normal file
@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
|
||||
/**
|
||||
* Tests for download/file transfer functions.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class DownloadTest extends FileManagedTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The file URL generator.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileUrlGeneratorInterface
|
||||
*/
|
||||
protected $fileUrlGenerator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// This test currently frequently causes the SQLite database to lock, so
|
||||
// skip the test on SQLite until the issue can be resolved.
|
||||
// @todo Fix root cause and re-enable in
|
||||
// https://www.drupal.org/project/drupal/issues/3311587
|
||||
if (Database::getConnection()->driver() === 'sqlite') {
|
||||
$this->markTestSkipped('Test frequently causes a locked database on SQLite');
|
||||
}
|
||||
|
||||
$this->fileUrlGenerator = $this->container->get('file_url_generator');
|
||||
// Clear out any hook calls.
|
||||
FileTestHelper::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the public file transfer system.
|
||||
*/
|
||||
public function testPublicFileTransfer(): void {
|
||||
// Test generating a URL to a created file.
|
||||
$file = $this->createFile();
|
||||
$url = $this->fileUrlGenerator->generateAbsoluteString($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->assertEquals($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 = $this->fileUrlGenerator->generateAbsoluteString($filepath);
|
||||
$this->assertEquals($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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the private file transfer system.
|
||||
*/
|
||||
public function testPrivateFileTransferWithoutPageCache(): void {
|
||||
$this->doPrivateFileTransferTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the private file transfer system.
|
||||
*/
|
||||
protected function doPrivateFileTransferTest(): void {
|
||||
// Set file downloads to private so handler functions get called.
|
||||
|
||||
// Create a file.
|
||||
$contents = $this->randomMachineName(8);
|
||||
$file = $this->createFile($contents . '.txt', $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 = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
|
||||
|
||||
// Set file_test access header to allow the download.
|
||||
FileTestHelper::reset();
|
||||
FileTestHelper::setReturn('download', ['x-foo' => 'Bar']);
|
||||
$this->drupalGet($url);
|
||||
// Verify that header is set by file_test module on private download.
|
||||
$this->assertSession()->responseHeaderEquals('x-foo', 'Bar');
|
||||
// Verify that page cache is disabled on private file download.
|
||||
$this->assertSession()->responseHeaderDoesNotExist('x-drupal-cache');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Ensure hook_file_download is fired correctly.
|
||||
$this->assertEquals($file->getFileUri(), FileTestHelper::getCalls('download')[0][0]);
|
||||
|
||||
// Test that the file transferred correctly.
|
||||
$this->assertSame($contents, $this->getSession()->getPage()->getContent(), 'Contents of the file are correct.');
|
||||
$http_client = $this->getHttpClient();
|
||||
|
||||
// Try non-existent file.
|
||||
FileTestHelper::reset();
|
||||
$not_found_url = $this->fileUrlGenerator->generateAbsoluteString('private://' . $this->randomMachineName() . '.txt');
|
||||
$response = $http_client->head($not_found_url, ['http_errors' => FALSE]);
|
||||
$this->assertSame(404, $response->getStatusCode(), 'Correctly returned 404 response for a non-existent file.');
|
||||
// Assert that hook_file_download is not called.
|
||||
$this->assertEquals([], FileTestHelper::getCalls('download'));
|
||||
|
||||
// Having tried a non-existent file, try the original file again to ensure
|
||||
// it's returned instead of a 404 response.
|
||||
// Set file_test access header to allow the download.
|
||||
FileTestHelper::reset();
|
||||
FileTestHelper::setReturn('download', ['x-foo' => 'Bar']);
|
||||
$this->drupalGet($url);
|
||||
// Verify that header is set by file_test module on private download.
|
||||
$this->assertSession()->responseHeaderEquals('x-foo', 'Bar');
|
||||
// Verify that page cache is disabled on private file download.
|
||||
$this->assertSession()->responseHeaderDoesNotExist('x-drupal-cache');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Test that the file transferred correctly.
|
||||
$this->assertSame($contents, $this->getSession()->getPage()->getContent(), 'Contents of the file are correct.');
|
||||
|
||||
// Deny access to all downloads via a -1 header.
|
||||
FileTestHelper::setReturn('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 requesting the private file URL without a file specified.
|
||||
FileTestHelper::reset();
|
||||
$this->drupalGet('/system/files');
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
// Assert that hook_file_download is not called.
|
||||
$this->assertEquals([], FileTestHelper::getCalls('download'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test FileUrlGeneratorInterface::generateString()
|
||||
*/
|
||||
public function testFileCreateUrl(): void {
|
||||
// "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.
|
||||
// cSpell:disable-next-line
|
||||
"éøïвβ中國書۞";
|
||||
$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->assertEquals('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', $this->fileUrlGenerator->generateString('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from the URL generated by generateString().
|
||||
*
|
||||
* Create a file with the specified scheme, directory and filename; check that
|
||||
* the URL generated by FileUrlGeneratorInterface::generateString() 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): void {
|
||||
// Convert $filename to a valid filename, i.e. strip characters not
|
||||
// supported by the filesystem, and create the file in the specified
|
||||
// directory.
|
||||
$filepath = \Drupal::service('file_system')->createFilename($filename, $directory);
|
||||
$directory_uri = $scheme . '://' . dirname($filepath);
|
||||
\Drupal::service('file_system')->prepareDirectory($directory_uri, FileSystemInterface::CREATE_DIRECTORY);
|
||||
$file = $this->createFile($filepath, NULL, $scheme);
|
||||
|
||||
$url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
|
||||
$this->assertEquals($expected_url, $url);
|
||||
|
||||
if ($scheme == 'private') {
|
||||
// Tell the implementation of hook_file_download() in file_test.module
|
||||
// that this file may be downloaded.
|
||||
FileTestHelper::setReturn('download', ['x-foo' => 'Bar']);
|
||||
}
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains(file_get_contents($file->getFileUri()));
|
||||
|
||||
$file->delete();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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(): void {
|
||||
$type = 'Article';
|
||||
$title = 'test page';
|
||||
|
||||
// Load the node form.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Create $type");
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => $title,
|
||||
'body[0][value]' => 'Test article',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("$type $title has been created.");
|
||||
$matches = [];
|
||||
if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
|
||||
$nid = end($matches);
|
||||
$this->assertNotEquals(0, $nid, 'The node ID was extracted from the URL.');
|
||||
$node = Node::load($nid);
|
||||
$this->assertNotNull($node, 'The node was loaded successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file submission for an anonymous visitor.
|
||||
*/
|
||||
public function testAnonymousNodeWithFile(): void {
|
||||
$type = 'Article';
|
||||
$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->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Create $type");
|
||||
|
||||
// Generate an image file.
|
||||
$image = $this->getTestFile('image');
|
||||
|
||||
// Submit the form.
|
||||
$edit = [
|
||||
'title[0][value]' => $title,
|
||||
'body[0][value]' => 'Test article',
|
||||
'files[field_image_0]' => $this->container->get('file_system')->realpath($image->getFileUri()),
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("$type $title has been created.");
|
||||
$matches = [];
|
||||
if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
|
||||
$nid = end($matches);
|
||||
$this->assertNotEquals(0, $nid, 'The node ID was extracted from the URL.');
|
||||
$node = Node::load($nid);
|
||||
$this->assertNotNull($node, 'The node was loaded successfully.');
|
||||
$this->assertFileExists(File::load($node->field_image->target_id)->getFileUri());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file submission for an anonymous visitor with a missing node title.
|
||||
*/
|
||||
public function testAnonymousNodeWithFileWithoutTitle(): void {
|
||||
$this->drupalLogout();
|
||||
$this->doTestNodeWithFileWithoutTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file submission for an authenticated user with a missing node title.
|
||||
*/
|
||||
public function testAuthenticatedNodeWithFileWithoutTitle(): void {
|
||||
$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(): void {
|
||||
$type = 'Article';
|
||||
$title = 'Test page';
|
||||
$this->createFileField('field_image', 'node', 'article', [], ['file_extensions' => 'txt png']);
|
||||
|
||||
// Load the node form.
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Create $type");
|
||||
|
||||
// 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->submitForm($edit, $label);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains("$type $title has been created.");
|
||||
$this->assertSession()->pageTextContains('Title field is required.');
|
||||
|
||||
// Submit the form again but this time with the missing title field. This
|
||||
// should still work.
|
||||
$edit = [
|
||||
'title[0][value]' => $title,
|
||||
];
|
||||
$this->submitForm($edit, $label);
|
||||
|
||||
// Confirm the final submission actually worked.
|
||||
$this->assertSession()->pageTextContains("$type $title has been created.");
|
||||
$matches = [];
|
||||
if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) {
|
||||
$nid = end($matches);
|
||||
$this->assertNotEquals(0, $nid, 'The node ID was extracted from the URL.');
|
||||
$node = Node::load($nid);
|
||||
$this->assertNotNull($node, 'The node was loaded successfully.');
|
||||
$this->assertFileExists(File::load($node->field_image->target_id)->getFileUri());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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). The Field UI 'field_' prefix
|
||||
* is not added to the field name.
|
||||
* @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.
|
||||
*/
|
||||
protected 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). The Field UI 'field_' prefix
|
||||
* is not added to the field name.
|
||||
* @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.
|
||||
*/
|
||||
protected 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();
|
||||
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay($entity_type, $bundle)
|
||||
->setComponent($name, [
|
||||
'type' => 'file_generic',
|
||||
'settings' => $widget_settings,
|
||||
])
|
||||
->save();
|
||||
// Assign display settings.
|
||||
\Drupal::service('entity_display.repository')->getViewDisplay($entity_type, $bundle)
|
||||
->setComponent($name, [
|
||||
'label' => 'hidden',
|
||||
'type' => 'file_default',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the display of file fields in node and views.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldDisplayTest extends FileFieldTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests normal formatter display on node display.
|
||||
*/
|
||||
public function testNodeDisplay(): void {
|
||||
$field_name = $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->drupalGet("admin/structure/types/manage/{$type_name}/display");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
// Verify that the field label is hidden when no file is attached.
|
||||
$this->assertSession()->pageTextNotContains($field_name);
|
||||
}
|
||||
|
||||
$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_type.manager')->getStorage('node');
|
||||
$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->assertSession()->responseContains($default_output);
|
||||
|
||||
// Turn the "display" option off and check that the file is no longer
|
||||
// displayed.
|
||||
$edit = [$field_name . '[0][display]' => FALSE];
|
||||
$this->drupalGet('node/' . $nid . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$this->assertSession()->responseNotContains($default_output);
|
||||
|
||||
// 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->drupalGet('node/' . $nid . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains($description);
|
||||
|
||||
// Ensure the filename in the link's title attribute is escaped.
|
||||
$this->assertSession()->responseContains('title="escaped-&-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->drupalGet("node/{$nid}/edit");
|
||||
$this->submitForm($edit_upload, 'Upload');
|
||||
|
||||
// Uncheck the display checkboxes and go to the preview.
|
||||
$edit[$field_name . '[0][display]'] = FALSE;
|
||||
$edit[$field_name . '[1][display]'] = FALSE;
|
||||
$this->submitForm($edit, 'Preview');
|
||||
$this->clickLink('Back to content editing');
|
||||
// First file.
|
||||
$this->assertSession()->responseContains($field_name . '[0][display]');
|
||||
// Second file.
|
||||
$this->assertSession()->responseContains($field_name . '[1][display]');
|
||||
$this->assertSession()->responseContains($field_name . '[1][description]');
|
||||
|
||||
// Check that the file fields don't contain duplicate HTML IDs.
|
||||
$this->assertSession()->pageContainsNoDuplicateId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests default display of File Field.
|
||||
*/
|
||||
public function testDefaultFileFieldDisplay(): void {
|
||||
$field_name = $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->assertSession()->fieldExists($field_name . '[0][display]');
|
||||
$this->assertSession()->checkboxNotChecked($field_name . '[0][display]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests description toggle for field instance configuration.
|
||||
*/
|
||||
public function testDescToggle(): void {
|
||||
$type_name = 'test';
|
||||
$field_type = 'file';
|
||||
$field_name = $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->drupalGet('admin/structure/types/add');
|
||||
$this->submitForm($edit, 'Save and manage fields');
|
||||
$field_edit = [
|
||||
'settings[description_field]' => TRUE,
|
||||
];
|
||||
$this->fieldUIAddNewField('/admin/structure/types/manage/' . $type_name, $field_name, $this->randomString(), $field_type, [], $field_edit);
|
||||
// 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->drupalGet('node/add/' . $type_name);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->assertSession()->pageTextContains('The description may be used as the label of the link to the file.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests description display of File Field.
|
||||
*/
|
||||
public function testDescriptionDefaultFileFieldDisplay(): void {
|
||||
$field_name = $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->drupalGet("node/{$nid}/edit");
|
||||
$this->submitForm([
|
||||
$field_name . '[0][description]' => $description,
|
||||
], 'Save');
|
||||
|
||||
// Load uncached node.
|
||||
\Drupal::entityTypeManager()->getStorage('node')->resetCache([$nid]);
|
||||
$node = Node::load($nid);
|
||||
|
||||
// Test default formatter.
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertSession()->elementTextContains('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl() . '"]', $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->assertSession()->elementTextContains('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl() . '"]', $description);
|
||||
|
||||
// Test that null file size is rendered as "Unknown".
|
||||
$nonexistent_file = File::create([
|
||||
'uri' => 'temporary://' . $this->randomMachineName() . '.txt',
|
||||
]);
|
||||
$nonexistent_file->save();
|
||||
$node->set($field_name, $nonexistent_file->id());
|
||||
$node->save();
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertSession()->elementTextEquals('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl() . '"]/../../../td[2]', 'Unknown');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
/**
|
||||
* Tests file formatter access.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldFormatterAccessTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'file', 'field_ui', 'file_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the custom access handler is invoked.
|
||||
*/
|
||||
public function testFileAccessHandler(): void {
|
||||
$type_name = 'article';
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
\Drupal::state()->set('file_test_alternate_access_handler', TRUE);
|
||||
\Drupal::entityTypeManager()->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));
|
||||
}
|
||||
|
||||
}
|
||||
100
web/core/modules/file/tests/src/Functional/FileFieldPathTest.php
Normal file
100
web/core/modules/file/tests/src/Functional/FileFieldPathTest.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* Tests that files are uploaded to proper locations.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldPathTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the normal formatter display on node display.
|
||||
*/
|
||||
public function testUploadPath(): void {
|
||||
/** @var \Drupal\node\NodeStorageInterface $node_storage */
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$field_name = $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 = $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(\Drupal::time()->getRequestTime(), 'custom', 'Y') . '-' .
|
||||
$date_formatter->format(\Drupal::time()->getRequestTime(), 'custom', 'm') . '/' .
|
||||
$test_file->getFilename();
|
||||
$this->assertPathMatch($expected_filename, $node_file->getFileUri(), "The file {$node_file->getFileUri()} was uploaded to the correct path.");
|
||||
|
||||
// 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 = $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(), "The file {$node_file->getFileUri()} was uploaded to the correct path.");
|
||||
|
||||
// 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 = $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(), "The file {$node_file->getFileUri()} was uploaded to the correct path with token replacements.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertPathMatch(string $expected_path, string $actual_path, string $message): void {
|
||||
// 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 = (bool) preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path);
|
||||
$this->assertTrue($result, $message);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests RSS enclosure formatter display for RSS feeds.
|
||||
*/
|
||||
public function testFileFieldRSSContent(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$field_name = $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->submitForm($edit, '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->submitForm($edit, '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 = $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');
|
||||
$selector = sprintf(
|
||||
'//enclosure[@url="%s" and @length="%s" and @type="%s"]',
|
||||
$node_file->createFileUrl(FALSE),
|
||||
$node_file->getSize(),
|
||||
$node_file->getMimeType()
|
||||
);
|
||||
$this->assertNotEmpty($this->getSession()->getDriver()->find($selector), 'File field RSS enclosure is displayed when viewing the RSS feed.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* Tests creating and deleting revisions with files attached.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldRevisionTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* 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(): void {
|
||||
// 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_type.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
$field_name = $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 = $node_storage->load($nid);
|
||||
$node_file_r1 = File::load($node->{$field_name}->target_id);
|
||||
$node_vid_r1 = $node->getRevisionId();
|
||||
$this->assertFileExists($node_file_r1->getFileUri());
|
||||
$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 = $node_storage->load($nid);
|
||||
$node_file_r2 = File::load($node->{$field_name}->target_id);
|
||||
$node_vid_r2 = $node->getRevisionId();
|
||||
$this->assertFileExists($node_file_r2->getFileUri());
|
||||
$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_storage->loadRevision($node_vid_r1);
|
||||
$current_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertEquals($node_file_r1->id(), $current_file->id(), 'Original file still in place after replacing file in new revision.');
|
||||
$this->assertFileExists($node_file_r1->getFileUri());
|
||||
$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->drupalGet('node/' . $nid . '/edit');
|
||||
$this->submitForm(['revision' => '1'], 'Save');
|
||||
$node = $node_storage->load($nid);
|
||||
$node_file_r3 = File::load($node->{$field_name}->target_id);
|
||||
$node_vid_r3 = $node->getRevisionId();
|
||||
$this->assertEquals($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->drupalGet('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert');
|
||||
$this->submitForm([], 'Revert');
|
||||
$node = $node_storage->load($nid);
|
||||
$node_file_r4 = File::load($node->{$field_name}->target_id);
|
||||
$this->assertEquals($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->drupalGet('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete');
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertFileExists($node_file_r3->getFileUri());
|
||||
$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->drupalGet('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete');
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertFileExists($node_file_r3->getFileUri());
|
||||
$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();
|
||||
|
||||
// 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. We use an UPDATE statement because using the API
|
||||
// would set the timestamp.
|
||||
$connection = Database::getConnection();
|
||||
$connection->update('file_managed')
|
||||
->fields([
|
||||
'changed' => \Drupal::time()->getRequestTime() - ($this->config('system.file')->get('temporary_maximum_age') + 1),
|
||||
])
|
||||
->condition('fid', $node_file_r3->id())
|
||||
->execute();
|
||||
\Drupal::service('cron')->run();
|
||||
|
||||
$this->assertFileDoesNotExist($node_file_r3->getFileUri());
|
||||
$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->drupalGet('node/' . $nid . '/delete');
|
||||
$this->submitForm([], '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. We use an UPDATE statement because using the API
|
||||
// would set the timestamp.
|
||||
$connection->update('file_managed')
|
||||
->fields([
|
||||
'changed' => \Drupal::time()->getRequestTime() - ($this->config('system.file')->get('temporary_maximum_age') + 1),
|
||||
])
|
||||
->condition('fid', $node_file_r1->id())
|
||||
->execute();
|
||||
\Drupal::service('cron')->run();
|
||||
$this->assertFileDoesNotExist($node_file_r1->getFileUri());
|
||||
$this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.');
|
||||
}
|
||||
|
||||
}
|
||||
250
web/core/modules/file/tests/src/Functional/FileFieldTestBase.php
Normal file
250
web/core/modules/file/tests/src/Functional/FileFieldTestBase.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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(): void {
|
||||
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
|
||||
* The new unsaved file entity.
|
||||
*/
|
||||
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) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay('node', $type_name)
|
||||
->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 int|string $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 int|string $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_type.manager')->getStorage('node');
|
||||
if (is_numeric($nid_or_type)) {
|
||||
$nid = $nid_or_type;
|
||||
$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 = $node_storage->load($nid);
|
||||
$this->assertNotEquals($nid, $node->getRevisionId(), 'Node revision exists.');
|
||||
}
|
||||
$this->drupalGet("node/$nid/edit");
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// 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});
|
||||
foreach ($files as $i => $file) {
|
||||
$delta = $field_num + $i;
|
||||
$file_path = $this->container->get('file_system')->realpath($file->getFileUri());
|
||||
$name = 'files[' . $field_name . '_' . $delta . ']';
|
||||
if ($field_storage->getCardinality() != 1) {
|
||||
$name .= '[]';
|
||||
}
|
||||
if (count($files) == 1) {
|
||||
$edit[$name] = $file_path;
|
||||
}
|
||||
else {
|
||||
$page->attachFileToField($name, $file_path);
|
||||
$this->submitForm([], 'Upload');
|
||||
}
|
||||
}
|
||||
|
||||
$this->submitForm($edit, '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->drupalGet('node/' . $nid . '/edit');
|
||||
$this->submitForm([], 'Remove');
|
||||
$this->submitForm($edit, '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->drupalGet('node/' . $nid . '/edit');
|
||||
$this->submitForm([], 'Remove');
|
||||
$this->submitForm($edit, 'Save');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a file exists in the database.
|
||||
*/
|
||||
public function assertFileEntryExists($file, $message = NULL) {
|
||||
$this->container->get('entity_type.manager')->getStorage('file')->resetCache();
|
||||
$db_file = File::load($file->id());
|
||||
$message = $message ?? sprintf('File %s exists in database at the correct path.', $file->getFileUri());
|
||||
$this->assertEquals($file->getFileUri(), $db_file->getFileUri(), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a file does not exist in the database.
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file to be validated.
|
||||
* @param string|null $message
|
||||
* (optional) A message to display with the assertion.
|
||||
*/
|
||||
public function assertFileEntryNotExists(FileInterface $file, ?string $message = NULL): void {
|
||||
$this->container->get('entity_type.manager')->getStorage('file')->resetCache();
|
||||
$message = $message ?? sprintf('File %s exists in database at the correct path.', $file->getFileUri());
|
||||
$this->assertNull(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 = $message ?? sprintf('File %s is permanent.', $file->getFileUri());
|
||||
$this->assertTrue($file->isPermanent(), $message);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\StringTranslation\ByteSizeMarkup;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* Tests file field validation functions.
|
||||
*
|
||||
* Values validated include the file type, max file size, max size per node,
|
||||
* and whether the field is required.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldValidateTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the required property on file fields.
|
||||
*/
|
||||
public function testRequired(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
$field_name = $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->drupalGet('node/add/' . $type_name);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field->getLabel()} field is required.");
|
||||
|
||||
// Create a new node with the uploaded file.
|
||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||
$this->assertNotFalse($nid, "uploadNodeFile({$test_file->getFileUri()}, $field_name, $type_name) succeeded");
|
||||
|
||||
$node = $node_storage->load($nid);
|
||||
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$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->drupalGet('node/add/' . $type_name);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field->getLabel()} field is required.");
|
||||
|
||||
// Create a new node with the uploaded file into the multivalue field.
|
||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||
$node = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$this->assertFileEntryExists($node_file, 'File entry exists after uploading to the required multiple value field.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the max file size validator.
|
||||
*/
|
||||
public function testFileMaxSize(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
$field_name = $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 = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$this->assertFileEntryExists($node_file, sprintf('File entry exists after uploading a file (%s) under the max limit (%s).', ByteSizeMarkup::create($small_file->getSize()), $max_filesize));
|
||||
|
||||
// Check that uploading the large file fails (1M limit).
|
||||
$this->uploadNodeFile($large_file, $field_name, $type_name);
|
||||
$filesize = ByteSizeMarkup::create($large_file->getSize());
|
||||
$maxsize = ByteSizeMarkup::create($file_limit);
|
||||
$this->assertSession()->pageTextContains("The file is {$filesize} exceeding the maximum file size of {$maxsize}.");
|
||||
}
|
||||
|
||||
// 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 = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$this->assertFileEntryExists($node_file, sprintf('File entry exists after uploading a file (%s) with no max limit.', ByteSizeMarkup::create($large_file->getSize())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file extension checking.
|
||||
*/
|
||||
public function testFileExtension(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
|
||||
$test_file = $this->getTestFile('image');
|
||||
[, $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 = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$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);
|
||||
$this->assertSession()->pageTextContains("Only files with the following extensions are allowed: txt.");
|
||||
|
||||
// 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 = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$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(): void {
|
||||
$node_storage = $this->container->get('entity_type.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 = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$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->assertSession()->pageTextNotContains('Only files with the following extensions are allowed: txt.');
|
||||
$this->assertSession()->pageTextContains('Article ' . $node->getTitle() . ' has been updated.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,612 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests the file field widget with public and private files.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldWidgetTest extends FileFieldTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['comment', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary file, for a specific user.
|
||||
*
|
||||
* @param string $data
|
||||
* A string containing the contents of the file.
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The user of the file owner.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface
|
||||
* A file object, or FALSE on error.
|
||||
*/
|
||||
protected function createTemporaryFile($data, ?UserInterface $user = NULL) {
|
||||
/** @var \Drupal\file\FileRepositoryInterface $file_repository */
|
||||
$file_repository = \Drupal::service('file.repository');
|
||||
$file = $file_repository->writeData($data, "public://");
|
||||
|
||||
if ($file) {
|
||||
if ($user) {
|
||||
$file->setOwner($user);
|
||||
}
|
||||
else {
|
||||
$file->setOwner($this->adminUser);
|
||||
}
|
||||
// Change the file status to be temporary.
|
||||
$file->setTemporary();
|
||||
// Save the changes.
|
||||
$file->save();
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests upload and remove buttons for a single-valued File field.
|
||||
*/
|
||||
public function testSingleValuedWidget(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
|
||||
$test_file = $this->getTestFile('text');
|
||||
|
||||
// Create a new node with the uploaded file and ensure it got uploaded
|
||||
// successfully.
|
||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
|
||||
// Ensure the file can be downloaded.
|
||||
$this->drupalGet($node_file->createFileUrl());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Ensure the edit page has a remove button instead of an upload button.
|
||||
$this->drupalGet("node/$nid/edit");
|
||||
$this->assertSession()->buttonNotExists('Upload');
|
||||
$this->assertSession()->buttonExists('Remove');
|
||||
$this->submitForm([], 'Remove');
|
||||
|
||||
// Ensure the page now has an upload button instead of a remove button.
|
||||
$this->assertSession()->buttonNotExists('Remove');
|
||||
$this->assertSession()->buttonExists('Upload');
|
||||
// Test label has correct 'for' attribute.
|
||||
$input = $this->assertSession()->fieldExists("files[{$field_name}_0]");
|
||||
$this->assertSession()->elementExists('xpath', '//label[@for="' . $input->getAttribute('id') . '"]');
|
||||
|
||||
// Save the node and ensure it does not have the file.
|
||||
$this->submitForm([], 'Save');
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$this->assertEmpty($node->{$field_name}->target_id, 'File was successfully removed from the node.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests upload and remove buttons for multiple multi-valued File fields.
|
||||
*/
|
||||
public function testMultiValuedWidget(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
// Use explicit names instead of random names for those fields, because of a
|
||||
// bug in submitForm() with multiple file uploads in one form, where the
|
||||
// order of uploads depends on the order in which the upload elements are
|
||||
// added to the $form (which, in the current implementation of
|
||||
// FileStorage::listAll(), comes down to the alphabetical order on field
|
||||
// names).
|
||||
$field_name = 'test_file_field_1';
|
||||
$field_name2 = 'test_file_field_2';
|
||||
$cardinality = 3;
|
||||
$this->createFileField($field_name, 'node', $type_name, ['cardinality' => $cardinality]);
|
||||
$this->createFileField($field_name2, 'node', $type_name, ['cardinality' => $cardinality]);
|
||||
|
||||
$test_file = $this->getTestFile('text');
|
||||
|
||||
// Visit the node creation form, and upload 3 files for each field. Since
|
||||
// the field has cardinality of 3, ensure the "Upload" button is displayed
|
||||
// until after the 3rd file, and after that, isn't displayed. Because
|
||||
// the last button with a given name is triggered by default, upload to the
|
||||
// second field first.
|
||||
$this->drupalGet("node/add/$type_name");
|
||||
foreach ([$field_name2, $field_name] as $each_field_name) {
|
||||
for ($delta = 0; $delta < 3; $delta++) {
|
||||
$edit = ['files[' . $each_field_name . '_' . $delta . '][]' => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
// If the Upload button doesn't exist, submitForm() will
|
||||
// automatically fail with an assertion message.
|
||||
$this->submitForm($edit, 'Upload');
|
||||
}
|
||||
}
|
||||
$this->assertSession()->buttonNotExists('Upload');
|
||||
|
||||
$num_expected_remove_buttons = 6;
|
||||
|
||||
foreach ([$field_name, $field_name2] as $current_field_name) {
|
||||
// How many uploaded files for the current field are remaining.
|
||||
$remaining = 3;
|
||||
// Test clicking each "Remove" button. For extra robustness, test them out
|
||||
// of sequential order. They are 0-indexed, and get renumbered after each
|
||||
// iteration, so [1, 1, 0] means:
|
||||
// - First remove the 2nd file.
|
||||
// - Then remove what is then the 2nd file (was originally the 3rd file).
|
||||
// - Then remove the first file.
|
||||
foreach ([1, 1, 0] as $delta) {
|
||||
// Ensure we have the expected number of Remove buttons, and that they
|
||||
// are numbered sequentially.
|
||||
$buttons = $this->xpath('//input[@type="submit" and @value="Remove"]');
|
||||
$this->assertCount($num_expected_remove_buttons, $buttons, "There are $num_expected_remove_buttons \"Remove\" buttons displayed.");
|
||||
foreach ($buttons as $i => $button) {
|
||||
$key = $i >= $remaining ? $i - $remaining : $i;
|
||||
$check_field_name = $field_name2;
|
||||
if ($current_field_name == $field_name && $i < $remaining) {
|
||||
$check_field_name = $field_name;
|
||||
}
|
||||
|
||||
$this->assertSame($check_field_name . '_' . $key . '_remove_button', $button->getAttribute('name'));
|
||||
}
|
||||
|
||||
// "Click" the remove button (emulating either a nojs or js submission).
|
||||
$button_name = $current_field_name . '_' . $delta . '_remove_button';
|
||||
$this->getSession()->getPage()->findButton($button_name)->press();
|
||||
$num_expected_remove_buttons--;
|
||||
$remaining--;
|
||||
|
||||
// Ensure an "Upload" button for the current field is displayed with the
|
||||
// correct name.
|
||||
$upload_button_name = $current_field_name . '_' . $remaining . '_upload_button';
|
||||
$button = $this->assertSession()->buttonExists($upload_button_name);
|
||||
$this->assertSame('Upload', $button->getValue());
|
||||
|
||||
// Ensure only at most one button per field is displayed.
|
||||
$expected = $current_field_name == $field_name ? 1 : 2;
|
||||
$this->assertSession()->elementsCount('xpath', '//input[@type="submit" and @value="Upload"]', $expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the page now has no Remove buttons.
|
||||
$this->assertSession()->buttonNotExists('Remove');
|
||||
|
||||
// Save the node and ensure it does not have any files.
|
||||
$this->submitForm(['title[0][value]' => $this->randomMachineName()], 'Save');
|
||||
preg_match('/node\/([0-9])/', $this->getUrl(), $matches);
|
||||
$nid = $matches[1];
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$this->assertEmpty($node->{$field_name}->target_id, 'Node was successfully saved without any files.');
|
||||
|
||||
// Try to upload more files than allowed on revision.
|
||||
$upload_files_node_revision = [$test_file, $test_file, $test_file, $test_file];
|
||||
foreach ($upload_files_node_revision as $i => $file) {
|
||||
$edit['files[test_file_field_1_0][' . $i . ']'] = \Drupal::service('file_system')->realpath($test_file->getFileUri());
|
||||
}
|
||||
|
||||
// @todo Replace after https://www.drupal.org/project/drupal/issues/2917885
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->assertSession()->fieldExists('files[test_file_field_1_0][]');
|
||||
$submit_xpath = $this->assertSession()->buttonExists('Save')->getXpath();
|
||||
$client = $this->getSession()->getDriver()->getClient();
|
||||
$form = $client->getCrawler()->filterXPath($submit_xpath)->form();
|
||||
$client->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $edit);
|
||||
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$this->assertCount($cardinality, $node->{$field_name}, 'More files than allowed could not be saved to node.');
|
||||
|
||||
$upload_files_node_creation = [$test_file, $test_file];
|
||||
// Try to upload multiple files, but fewer than the maximum.
|
||||
$nid = $this->uploadNodeFiles($upload_files_node_creation, $field_name, $type_name, TRUE, []);
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$this->assertSameSize($upload_files_node_creation, $node->{$field_name}, 'Node was successfully saved with multiple files.');
|
||||
|
||||
// Try to upload exactly the allowed number of files on revision.
|
||||
$this->uploadNodeFile($test_file, $field_name, $node->id(), 1);
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$this->assertCount($cardinality, $node->{$field_name}, 'Node was successfully revised to maximum number of files.');
|
||||
|
||||
// Try to upload exactly the allowed number of files, new node.
|
||||
$upload_files = [$test_file, $test_file, $test_file];
|
||||
$nid = $this->uploadNodeFiles($upload_files, $field_name, $type_name, TRUE, []);
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$this->assertCount($cardinality, $node->{$field_name}, 'Node was successfully saved with maximum number of files.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a file field with a "Private files" upload destination setting.
|
||||
*/
|
||||
public function testPrivateFileSetting(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
// Grant the admin user required permissions.
|
||||
user_role_grant_permissions($this->adminUser->roles[0]->target_id, ['administer node fields']);
|
||||
|
||||
$type_name = 'article';
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
$field = FieldConfig::loadByName('node', $type_name, $field_name);
|
||||
$field_id = $field->id();
|
||||
|
||||
$test_file = $this->getTestFile('text');
|
||||
|
||||
// Change the field setting to make its files private, and upload a file.
|
||||
$edit = ['field_storage[subform][settings][uri_scheme]' => 'private'];
|
||||
$this->drupalGet("admin/structure/types/manage/{$type_name}/fields/{$field_id}");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||
$node = $node_storage->loadUnchanged($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
|
||||
// Ensure the private file is available to the user who uploaded it.
|
||||
$this->drupalGet($node_file->createFileUrl());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Ensure we can't change 'uri_scheme' field settings while there are some
|
||||
// entities with uploaded files.
|
||||
$this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id");
|
||||
$this->assertSession()->fieldDisabled("edit-field-storage-subform-settings-uri-scheme-public");
|
||||
|
||||
// Delete node and confirm that setting could be changed.
|
||||
$node->delete();
|
||||
$this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id");
|
||||
$this->assertSession()->fieldEnabled("edit-field-storage-subform-settings-uri-scheme-public");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that download restrictions on private files work on comments.
|
||||
*/
|
||||
public function testPrivateFileComment(): void {
|
||||
$user = $this->drupalCreateUser(['access comments']);
|
||||
|
||||
// Grant the admin user required comment permissions.
|
||||
$roles = $this->adminUser->getRoles();
|
||||
user_role_grant_permissions($roles[1], ['administer comment fields', 'administer comments']);
|
||||
|
||||
// Revoke access comments permission from anon user, grant post to
|
||||
// authenticated.
|
||||
user_role_revoke_permissions(RoleInterface::ANONYMOUS_ID, ['access comments']);
|
||||
user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, ['post comments', 'skip comment approval']);
|
||||
|
||||
// Create a new field.
|
||||
$this->addDefaultCommentField('node', 'article');
|
||||
|
||||
$name = $this->randomMachineName();
|
||||
$label = $this->randomMachineName();
|
||||
$storage_edit = ['settings[uri_scheme]' => 'private'];
|
||||
$this->fieldUIAddNewField('admin/structure/comment/manage/comment', $name, $label, 'file', $storage_edit);
|
||||
|
||||
// Manually clear cache on the tester side.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
// Create node.
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
|
||||
// Add a comment with a file.
|
||||
$text_file = $this->getTestFile('text');
|
||||
$edit = [
|
||||
'files[field_' . $name . '_' . 0 . ']' => \Drupal::service('file_system')->realpath($text_file->getFileUri()),
|
||||
'comment_body[0][value]' => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Get the comment ID.
|
||||
preg_match('/comment-([0-9]+)/', $this->getUrl(), $matches);
|
||||
$cid = $matches[1];
|
||||
|
||||
// Log in as normal user.
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$comment = Comment::load($cid);
|
||||
$comment_file = $comment->{'field_' . $name}->entity;
|
||||
$this->assertFileExists($comment_file->getFileUri());
|
||||
// Test authenticated file download.
|
||||
$url = $comment_file->createFileUrl();
|
||||
$this->assertNotNull($url, 'Confirmed that the URL is valid');
|
||||
$this->drupalGet($comment_file->createFileUrl());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Ensure that the anonymous user cannot download the file.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet($comment_file->createFileUrl());
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Unpublishes node.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$edit = ['status[value]' => FALSE];
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Ensures normal user can no longer download the file.
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet($comment_file->createFileUrl());
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation with the Upload button.
|
||||
*/
|
||||
public function testWidgetValidation(): void {
|
||||
$type_name = 'article';
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
$this->updateFileField($field_name, $type_name, ['file_extensions' => 'txt']);
|
||||
|
||||
// Create node and prepare files for upload.
|
||||
$node = $this->drupalCreateNode(['type' => 'article']);
|
||||
$nid = $node->id();
|
||||
$this->drupalGet("node/$nid/edit");
|
||||
$test_file_text = $this->getTestFile('text');
|
||||
$test_file_image = $this->getTestFile('image');
|
||||
$name = 'files[' . $field_name . '_0]';
|
||||
|
||||
// Upload file with incorrect extension, check for validation error.
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($test_file_image->getFileUri());
|
||||
$this->submitForm($edit, 'Upload');
|
||||
|
||||
$this->assertSession()->pageTextContains("Only files with the following extensions are allowed: txt.");
|
||||
|
||||
// Upload file with correct extension, check that error message is removed.
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($test_file_text->getFileUri());
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$this->assertSession()->pageTextNotContains("Only files with the following extensions are allowed: txt.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file widget element.
|
||||
*/
|
||||
public function testWidgetElement(): void {
|
||||
$field_name = $this->randomMachineName();
|
||||
$html_name = str_replace('_', '-', $field_name);
|
||||
$this->createFileField($field_name, 'node', 'article', ['cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED]);
|
||||
$file = $this->getTestFile('text');
|
||||
$xpath = "//details[@data-drupal-selector='edit-$html_name']/table";
|
||||
|
||||
$this->drupalGet('node/add/article');
|
||||
|
||||
// If the field has no item, the table should not be visible.
|
||||
$this->assertSession()->elementNotExists('xpath', $xpath);
|
||||
|
||||
// Upload a file.
|
||||
$edit['files[' . $field_name . '_0][]'] = $this->container->get('file_system')->realpath($file->getFileUri());
|
||||
$this->submitForm($edit, "{$field_name}_0_upload_button");
|
||||
|
||||
// If the field has at least one item, the table should be visible.
|
||||
$this->assertSession()->elementsCount('xpath', $xpath, 1);
|
||||
|
||||
// Test for AJAX error when using progress bar on file field widget.
|
||||
$http_client = $this->getHttpClient();
|
||||
$key = $this->randomMachineName();
|
||||
$post_request = $http_client->request('POST', $this->buildUrl('file/progress/' . $key), [
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
$this->assertNotEquals(500, $post_request->getStatusCode());
|
||||
$body = Json::decode($post_request->getBody());
|
||||
$this->assertStringContainsString('Starting upload...', $body['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests exploiting the temporary file removal of another user using fid.
|
||||
*/
|
||||
public function testTemporaryFileRemovalExploit(): void {
|
||||
// Create a victim user.
|
||||
$victim_user = $this->drupalCreateUser();
|
||||
|
||||
// Create an attacker user.
|
||||
$attacker_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
]);
|
||||
|
||||
// Log in as the attacker user.
|
||||
$this->drupalLogin($attacker_user);
|
||||
|
||||
// Perform tests using the newly created users.
|
||||
$this->doTestTemporaryFileRemovalExploit($victim_user, $attacker_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests exploiting the temporary file removal for anonymous users using fid.
|
||||
*/
|
||||
public function testTemporaryFileRemovalExploitAnonymous(): void {
|
||||
// Set up an anonymous victim user.
|
||||
$victim_user = User::getAnonymousUser();
|
||||
|
||||
// Set up an anonymous attacker user.
|
||||
$attacker_user = User::getAnonymousUser();
|
||||
|
||||
// Set up permissions for anonymous attacker user.
|
||||
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [
|
||||
'access content' => TRUE,
|
||||
'create article content' => TRUE,
|
||||
'edit any article content' => TRUE,
|
||||
]);
|
||||
|
||||
// Log out so as to be the anonymous attacker user.
|
||||
$this->drupalLogout();
|
||||
|
||||
// Perform tests using the newly set up anonymous users.
|
||||
$this->doTestTemporaryFileRemovalExploit($victim_user, $attacker_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests maximum upload file size validation.
|
||||
*/
|
||||
public function testMaximumUploadFileSizeValidation(): void {
|
||||
// Grant the admin user required permissions.
|
||||
user_role_grant_permissions($this->adminUser->roles[0]->target_id, ['administer node fields']);
|
||||
|
||||
$type_name = 'article';
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
/** @var \Drupal\Field\FieldConfigInterface $field */
|
||||
$field = FieldConfig::loadByName('node', $type_name, $field_name);
|
||||
$field_id = $field->id();
|
||||
$this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id");
|
||||
|
||||
// Tests that form validation trims the user input.
|
||||
$edit = ['settings[max_filesize]' => ' 5.1 megabytes '];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains('Saved ' . $field_name . ' configuration.');
|
||||
|
||||
// Reload the field config to check for the saved value.
|
||||
/** @var \Drupal\Field\FieldConfigInterface $field */
|
||||
$field = FieldConfig::loadByName('node', $type_name, $field_name);
|
||||
$settings = $field->getSettings();
|
||||
$this->assertEquals('5.1 megabytes', $settings['max_filesize'], 'The max filesize value had been trimmed on save.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuring file field's allowed file extensions setting.
|
||||
*/
|
||||
public function testFileExtensionsSetting(): void {
|
||||
// Grant the admin user required permissions.
|
||||
user_role_grant_permissions($this->adminUser->roles[0]->target_id, ['administer node fields']);
|
||||
|
||||
$type_name = 'article';
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
$field = FieldConfig::loadByName('node', $type_name, $field_name);
|
||||
$field_id = $field->id();
|
||||
|
||||
// By default allowing .php files without .txt is not permitted.
|
||||
$this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id");
|
||||
$edit = ['settings[file_extensions]' => 'jpg php'];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains('Add txt to the list of allowed extensions to securely upload files with a php extension. The txt extension will then be added automatically.');
|
||||
|
||||
// Test allowing .php and .txt.
|
||||
$edit = ['settings[file_extensions]' => 'jpg php txt'];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains('Saved ' . $field_name . ' configuration.');
|
||||
|
||||
// If the system is configured to allow insecure uploads, .txt is not
|
||||
// required when allowing .php.
|
||||
$this->config('system.file')->set('allow_insecure_uploads', TRUE)->save();
|
||||
$this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id");
|
||||
$edit = ['settings[file_extensions]' => 'jpg php'];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains('Saved ' . $field_name . ' configuration.');
|
||||
|
||||
// Check that a file extension with an underscore can be configured.
|
||||
$edit = [
|
||||
'settings[file_extensions]' => 'x_t x.t xt x_y_t',
|
||||
];
|
||||
$this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id");
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$field = FieldConfig::loadByName('node', $type_name, $field_name);
|
||||
$this->assertEquals('x_t x.t xt x_y_t', $field->getSetting('file_extensions'));
|
||||
|
||||
// Check that a file field with an invalid value in allowed extensions
|
||||
// property throws an error message.
|
||||
$invalid_extensions = ['x_.t', 'x._t', 'xt_', 'x__t', '_xt'];
|
||||
foreach ($invalid_extensions as $value) {
|
||||
$edit = [
|
||||
'settings[file_extensions]' => $value,
|
||||
];
|
||||
$this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id");
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("The list of allowed extensions is not valid. Allowed characters are a-z, 0-9, '.', and '_'. The first and last characters cannot be '.' or '_', and these two characters cannot appear next to each other. Separate extensions with a comma or space.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for testing exploiting the temporary file removal using fid.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $victim_user
|
||||
* The victim user.
|
||||
* @param \Drupal\user\UserInterface $attacker_user
|
||||
* The attacker user.
|
||||
*/
|
||||
protected function doTestTemporaryFileRemovalExploit(UserInterface $victim_user, UserInterface $attacker_user): void {
|
||||
$type_name = 'article';
|
||||
$field_name = 'test_file_field';
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
|
||||
$test_file = $this->getTestFile('text');
|
||||
$type = 'no-js';
|
||||
// Create a temporary file owned by the victim user. This will be as if
|
||||
// they had uploaded the file, but not saved the node they were editing
|
||||
// or creating.
|
||||
$victim_tmp_file = $this->createTemporaryFile('some text', $victim_user);
|
||||
$victim_tmp_file = File::load($victim_tmp_file->id());
|
||||
$this->assertTrue($victim_tmp_file->isTemporary(), 'New file saved to disk is temporary.');
|
||||
$this->assertNotEmpty($victim_tmp_file->id(), 'New file has an fid.');
|
||||
$this->assertEquals($victim_user->id(), $victim_tmp_file->getOwnerId(), 'New file belongs to the victim.');
|
||||
|
||||
// Have attacker create a new node with a different uploaded file and
|
||||
// ensure it got uploaded successfully.
|
||||
$edit = [
|
||||
'title[0][value]' => $type . '-title',
|
||||
];
|
||||
|
||||
// Attach a file to a node.
|
||||
$edit['files[' . $field_name . '_0]'] = $this->container->get('file_system')->realpath($test_file->getFileUri());
|
||||
$this->drupalGet(Url::fromRoute('node.add', ['node_type' => $type_name]));
|
||||
$this->submitForm($edit, 'Save');
|
||||
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
|
||||
/** @var \Drupal\file\FileInterface $node_file */
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file->getFileUri());
|
||||
$this->assertEquals($attacker_user->id(), $node_file->getOwnerId(), 'New file belongs to the attacker.');
|
||||
|
||||
// Ensure the file can be downloaded.
|
||||
$this->drupalGet($node_file->createFileUrl());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// "Click" the remove button (emulating either a nojs or js submission).
|
||||
// In this POST request, the attacker "guesses" the fid of the victim's
|
||||
// temporary file and uses that to remove this file.
|
||||
$this->drupalGet($node->toUrl('edit-form'));
|
||||
|
||||
$file_id_field = $this->assertSession()->hiddenFieldExists($field_name . '[0][fids]');
|
||||
$file_id_field->setValue((string) $victim_tmp_file->id());
|
||||
$this->submitForm([], 'Remove');
|
||||
|
||||
// The victim's temporary file should not be removed by the attacker's
|
||||
// POST request.
|
||||
$this->assertFileExists($victim_tmp_file->getFileUri());
|
||||
}
|
||||
|
||||
}
|
||||
283
web/core/modules/file/tests/src/Functional/FileListingTest.php
Normal file
283
web/core/modules/file/tests/src/Functional/FileListingTest.php
Normal file
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\entity_test\Entity\EntityTestConstraints;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests file listing page functionality.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileListingTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['views', 'file', 'image', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An authenticated user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $baseUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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',
|
||||
'delete any file',
|
||||
]);
|
||||
$this->baseUser = $this->drupalCreateUser();
|
||||
$this->createFileField('file', 'node', 'article', [], ['file_extensions' => 'txt png']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates total count of usages for a file.
|
||||
*
|
||||
* @param array $usage
|
||||
* Array of file usage information as returned from file_usage subsystem.
|
||||
*
|
||||
* @return int
|
||||
* Total usage count.
|
||||
*/
|
||||
protected function sumUsages($usage): int {
|
||||
$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(): void {
|
||||
$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->assertSession()->statusCodeEquals(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->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('No files available.');
|
||||
$this->drupalGet('admin/content/files');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Create a file with no usage.
|
||||
$file = $this->createFile();
|
||||
|
||||
$this->drupalGet('admin/content/files/usage/' . $file->id());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->titleEquals('File usage information for ' . $file->getFilename() . ' | Drupal');
|
||||
|
||||
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->submitForm($edit, 'Save');
|
||||
$node = Node::load($node->id());
|
||||
}
|
||||
|
||||
$this->drupalGet('admin/content/files');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$file = File::load($node->file->target_id);
|
||||
$this->assertSession()->pageTextContains($file->getFilename());
|
||||
$this->assertSession()->linkByHrefExists($file->createFileUrl());
|
||||
$this->assertSession()->linkByHrefExists('admin/content/files/usage/' . $file->id());
|
||||
$this->assertSession()->linkByHrefExists($file->toUrl('delete-form')->toString());
|
||||
}
|
||||
$this->assertSession()->elementTextNotContains('css', '.views-element-container table', 'Temporary');
|
||||
$this->assertSession()->elementTextContains('css', '.views-element-container table', '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->assertSession()->responseContains('admin/content/files/usage/' . $file->id() . '">' . $usage);
|
||||
|
||||
$file = File::load($used_file);
|
||||
$usage = $this->sumUsages($file_usage->listUsage($file));
|
||||
$this->assertSession()->responseContains('admin/content/files/usage/' . $file->id() . '">' . $usage);
|
||||
|
||||
$this->assertSession()->elementsCount('xpath', "//td[contains(@class, 'views-field-status') and contains(text(), 'Temporary')]", 1);
|
||||
|
||||
// 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->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains($node->getTitle());
|
||||
// Verify that registering entity type is found on usage page.
|
||||
$this->assertSession()->pageTextContains('node');
|
||||
// Verify that registering module is found on usage page.
|
||||
$this->assertSession()->pageTextContains('file');
|
||||
foreach ($usage as $module) {
|
||||
foreach ($module as $entity_type) {
|
||||
foreach ($entity_type as $entity) {
|
||||
// Verify that usage count is found on usage page.
|
||||
$this->assertSession()->pageTextContains($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertSession()->linkByHrefExists('node/' . $node->id(), 0, 'Link to registering entity found on usage page.');
|
||||
}
|
||||
|
||||
// Log in as another user that has access to the file list but cannot delete
|
||||
// files.
|
||||
$role_id = $this->drupalCreateRole([
|
||||
'access files overview',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($this->drupalCreateUser(values: ['roles' => [$role_id]]));
|
||||
|
||||
$this->drupalGet('admin/content/files');
|
||||
foreach ($nodes as $node) {
|
||||
$file = File::load($node->file->target_id);
|
||||
$this->assertSession()->pageTextContains($file->getFilename());
|
||||
$this->assertSession()->linkByHrefExists($file->createFileUrl());
|
||||
$this->assertSession()->linkByHrefExists('admin/content/files/usage/' . $file->id());
|
||||
$this->assertSession()->linkByHrefNotExists($file->toUrl('delete-form')->toString());
|
||||
}
|
||||
// Give the user's role permission to delete files.
|
||||
Role::load($role_id)->grantPermission('delete any file')->save();
|
||||
$this->drupalGet('admin/content/files');
|
||||
foreach ($nodes as $node) {
|
||||
$file = File::load($node->file->target_id);
|
||||
$this->assertSession()->pageTextContains($file->getFilename());
|
||||
$this->assertSession()->linkByHrefExists($file->createFileUrl());
|
||||
$this->assertSession()->linkByHrefExists('admin/content/files/usage/' . $file->id());
|
||||
$this->assertSession()->linkByHrefExists($file->toUrl('delete-form')->toString());
|
||||
}
|
||||
// Load the page in a definite order.
|
||||
$this->drupalGet('admin/content/files', ['query' => ['order' => 'filename', 'sort' => 'asc']]);
|
||||
$this->clickLink('Delete');
|
||||
$file_uri = File::load(1)->getFileUri();
|
||||
$this->assertSession()->addressMatches('#file/1/delete$#');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to delete the file druplicon.txt?');
|
||||
$this->assertFileExists($file_uri);
|
||||
$this->assertSession()->buttonExists('Delete')->press();
|
||||
$this->assertSession()->pageTextContains('The file druplicon.txt has been deleted.');
|
||||
$this->assertFileDoesNotExist($file_uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file listing usage page for entities with no canonical link template.
|
||||
*/
|
||||
public function testFileListingUsageNoLink(): void {
|
||||
// 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();
|
||||
EntityTestHelper::createBundle($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->assertSession()->statusCodeEquals(200);
|
||||
// Entity name should be displayed.
|
||||
$this->assertSession()->pageTextContains($entity_name);
|
||||
$this->assertSession()->linkNotExists($entity_name, 'Linked entity name not added to file usage listing.');
|
||||
$this->assertSession()->linkExists($node->getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and saves a test file.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* A file entity.
|
||||
*/
|
||||
protected function createFile(): EntityInterface {
|
||||
// Create a new file entity.
|
||||
$file = File::create([
|
||||
'uid' => 1,
|
||||
'filename' => 'druplicon.txt',
|
||||
'uri' => 'public://druplicon.txt',
|
||||
'filemime' => 'text/plain',
|
||||
'created' => 1,
|
||||
'changed' => 1,
|
||||
]);
|
||||
$file->setPermanent();
|
||||
file_put_contents($file->getFileUri(), 'hello world');
|
||||
|
||||
// Save it, inserting a new record.
|
||||
$file->save();
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the managed_file element type.
|
||||
*/
|
||||
public function testManagedFile(): void {
|
||||
// Check that $element['#size'] is passed to the child upload element.
|
||||
$this->drupalGet('file/test');
|
||||
$field = $this->assertSession()->fieldExists("files[nested_file]");
|
||||
$this->assertEquals(13, $field->getAttribute('size'));
|
||||
|
||||
// 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 . ']';
|
||||
|
||||
$this->drupalGet($path);
|
||||
|
||||
// Ensure the aria-describedby relationship works as expected.
|
||||
$input_id = Html::getId('edit_' . $input_base_name);
|
||||
$this->assertSession()->elementExists('css', '#' . $input_id . '--description');
|
||||
$this->assertSession()->elementExists('css', '[aria-describedby="' . $input_id . '--description"]');
|
||||
|
||||
// Submit without a file.
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains("The file ids are .");
|
||||
|
||||
// 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->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The form has become outdated.');
|
||||
$last_fid = $this->getLastFileId();
|
||||
$this->assertEquals($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->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$last_fid = $this->getLastFileId();
|
||||
$this->assertGreaterThan($last_fid_prior, $last_fid, 'New file got saved.');
|
||||
$this->assertSession()->pageTextContains("The file ids are $last_fid.");
|
||||
|
||||
// Submit no new input, but with a default file.
|
||||
$this->drupalGet($path . '/' . $last_fid);
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains("The file ids are $last_fid.");
|
||||
|
||||
// Upload, then Submit.
|
||||
$last_fid_prior = $this->getLastFileId();
|
||||
$this->drupalGet($path);
|
||||
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$last_fid = $this->getLastFileId();
|
||||
$this->assertGreaterThan($last_fid_prior, $last_fid, 'New file got uploaded.');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains("The file ids are $last_fid.");
|
||||
|
||||
// Remove, then Submit.
|
||||
$remove_button_title = $multiple ? 'Remove selected' : '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->submitForm($remove_edit, $remove_button_title);
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains("The file ids are .");
|
||||
|
||||
// Upload, then Remove, then Submit.
|
||||
$this->drupalGet($path);
|
||||
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$remove_edit = [];
|
||||
if ($multiple) {
|
||||
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]';
|
||||
$remove_edit = [$selected_checkbox => '1'];
|
||||
}
|
||||
$this->submitForm($remove_edit, $remove_button_title);
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains("The file ids are .");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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->submitForm($edit, 'Upload');
|
||||
$fid_list[] = $this->getLastFileId();
|
||||
$this->assertSession()->fieldExists("nested[file][file_{$fid_list[0]}][selected]");
|
||||
|
||||
// Add another file to the same upload field.
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$fid_list[] = $this->getLastFileId();
|
||||
$this->assertSession()->fieldExists("nested[file][file_{$fid_list[1]}][selected]");
|
||||
|
||||
// Save the entire form.
|
||||
$this->submitForm([], 'Save');
|
||||
// Check that two files are saved into a single multiple file element.
|
||||
$this->assertSession()->pageTextContains("The file ids are " . implode(',', $fid_list) . ".");
|
||||
|
||||
// Delete only the first file.
|
||||
$edit = [
|
||||
'nested[file][file_' . $fid_list[0] . '][selected]' => '1',
|
||||
];
|
||||
$this->drupalGet($path . '/' . implode(',', $fid_list));
|
||||
$this->submitForm($edit, 'Remove selected');
|
||||
|
||||
// Check that the first file has been deleted but not the second.
|
||||
$this->assertSession()->fieldNotExists("nested[file][file_{$fid_list[0]}][selected]");
|
||||
$this->assertSession()->fieldExists("nested[file][file_{$fid_list[1]}][selected]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that warning is shown if file on the field has been removed.
|
||||
*/
|
||||
public function testManagedFileRemoved(): void {
|
||||
$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->submitForm($edit, 'Upload');
|
||||
|
||||
$fid = $this->getLastFileId();
|
||||
$file = \Drupal::entityTypeManager()->getStorage('file')->load($fid);
|
||||
$file->delete();
|
||||
|
||||
$this->submitForm($edit, 'Upload');
|
||||
// We expect the title 'Managed <em>file & butter</em>' which got escaped
|
||||
// via a t() call before.
|
||||
$this->assertSession()->responseContains('The file referenced by the Managed <em>file & butter</em> field does not exist.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file names have leading . removed.
|
||||
*/
|
||||
public function testFileNameTrim(): void {
|
||||
file_put_contents('public://.leading-period.txt', $this->randomString(32));
|
||||
$last_fid_prior = $this->getLastFileId();
|
||||
$this->drupalGet('file/test/0/0/0');
|
||||
$this->submitForm(['files[file]' => \Drupal::service('file_system')->realpath('public://.leading-period.txt')], 'Save');
|
||||
$next_fid = $this->getLastFileId();
|
||||
$this->assertGreaterThan($last_fid_prior, $next_fid);
|
||||
$file = File::load($next_fid);
|
||||
$this->assertEquals('leading-period.txt', $file->getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a file entity can be saved when the file does not exist on disk.
|
||||
*/
|
||||
public function testFileRemovedFromDisk(): void {
|
||||
$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->submitForm($edit, 'Upload');
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
$fid = $this->getLastFileId();
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
$file = $this->container->get('entity_type.manager')->getStorage('file')->load($fid);
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
$this->assertTrue(\Drupal::service('file_system')->delete($file->getFileUri()));
|
||||
$file->save();
|
||||
$this->assertTrue($file->isPermanent());
|
||||
$file->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that unused permanent files can be used.
|
||||
*/
|
||||
public function testUnusedPermanentFileValidation(): void {
|
||||
|
||||
// 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->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextNotContains('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertSession()->pageTextContains('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->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertSession()->pageTextNotContains('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->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextNotContains('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertSession()->pageTextContains('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->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextNotContains('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertSession()->pageTextContains('The file ids are ' . $file->id());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Provides a base class for testing files with the file_test module.
|
||||
*/
|
||||
abstract class FileManagedTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file_test', 'file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Clear out any hook calls.
|
||||
FileTestHelper::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the specified file hooks were called only once.
|
||||
*
|
||||
* @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(FileTestHelper::getAllCalls()));
|
||||
|
||||
// Determine if there were any expected that were not called.
|
||||
$uncalled = array_diff($expected, $actual);
|
||||
if (count($uncalled)) {
|
||||
$this->assertTrue(FALSE, sprintf('Expected hooks %s to be called but %s was not called.', implode(', ', $expected), implode(', ', $uncalled)));
|
||||
}
|
||||
else {
|
||||
$this->assertTrue(TRUE, sprintf('All the expected hooks were called: %s', empty($expected) ? '(none)' : implode(', ', $expected)));
|
||||
}
|
||||
|
||||
// Determine if there were any unexpected calls.
|
||||
$unexpected = array_diff($actual, $expected);
|
||||
if (count($unexpected)) {
|
||||
$this->assertTrue(FALSE, sprintf('Unexpected hooks were called: %s.', 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(FileTestHelper::getCalls($hook));
|
||||
|
||||
if (!isset($message)) {
|
||||
if ($actual_count == $expected_count) {
|
||||
$message = "hook_file_$hook was called correctly.";
|
||||
}
|
||||
elseif ($expected_count == 0) {
|
||||
$message = "hook_file_$hook was not expected to be called but was actually called $actual_count time(s).";
|
||||
}
|
||||
else {
|
||||
$message = "hook_file_$hook was expected to be called $expected_count time(s) but was called $actual_count time(s).";
|
||||
}
|
||||
}
|
||||
$this->assertEquals($expected_count, $actual_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->assertEquals($before->id(), $after->id());
|
||||
$this->assertEquals($before->getOwner()->id(), $after->getOwner()->id());
|
||||
$this->assertEquals($before->getFilename(), $after->getFilename());
|
||||
$this->assertEquals($before->getFileUri(), $after->getFileUri());
|
||||
$this->assertEquals($before->getMimeType(), $after->getMimeType());
|
||||
$this->assertEquals($before->getSize(), $after->getSize());
|
||||
$this->assertEquals($before->isPermanent(), $after->isPermanent());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->assertNotEquals($file1->id(), $file2->id());
|
||||
$this->assertNotEquals($file1->getFileUri(), $file2->getFileUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->assertEquals($file1->id(), $file2->id());
|
||||
$this->assertEquals($file1->getFileUri(), $file2->getFileUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and saves a file, asserting that it was saved.
|
||||
*
|
||||
* @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.
|
||||
// Verify that the file was added to the database.
|
||||
$this->assertGreaterThan(0, $file->id());
|
||||
|
||||
\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.
|
||||
// cSpell:disable-next-line
|
||||
$filepath = 'Файл для тестирования ' . $this->randomMachineName();
|
||||
}
|
||||
if (!isset($scheme)) {
|
||||
$scheme = 'public';
|
||||
}
|
||||
$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->assertFileExists($filepath);
|
||||
return $filepath;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
|
||||
// cspell:ignore Scarlett Johansson
|
||||
|
||||
/**
|
||||
* Uploads files to translated nodes.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileOnTranslatedEntityTest extends FileFieldTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the file field used in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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 = $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.
|
||||
static::createLanguageFromLangcode('fr');
|
||||
static::createLanguageFromLangcode('nl');
|
||||
|
||||
// Enable translation for "Basic page" nodes.
|
||||
static::enableContentTranslation('node', 'page');
|
||||
static::setFieldTranslatable('node', 'page', $this->fieldName, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests synced file fields on translated nodes.
|
||||
*/
|
||||
public function testSyncedFiles(): void {
|
||||
// Verify that the file field on the "Basic page" node type is translatable.
|
||||
$definitions = \Drupal::service('entity_field.manager')->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->drupalGet('node/' . $default_language_node->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$first_fid = $this->getLastFileId();
|
||||
|
||||
// Translate the node into French: remove the existing file.
|
||||
$this->drupalGet('node/' . $default_language_node->id() . '/translations/add/en/fr');
|
||||
$this->submitForm([], '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->submitForm($edit, 'Save (this translation)');
|
||||
// This inspects the HTML after the post of the translation, the file
|
||||
// should be displayed on the original node.
|
||||
$this->assertSession()->responseContains('file--mime-text-plain');
|
||||
$second_fid = $this->getLastFileId();
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
|
||||
// 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->drupalGet('node/' . $default_language_node->id() . '/translations/add/en/nl');
|
||||
$this->submitForm([], '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->submitForm($edit, '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->assertSession()->responseContains('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->drupalGet('fr/node/' . $default_language_node->id() . '/edit');
|
||||
$this->submitForm([], '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->submitForm($edit, '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->drupalGet('nl/node/' . $default_language_node->id() . '/delete');
|
||||
$this->submitForm([], '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->drupalGet('node/' . $default_language_node->id() . '/delete');
|
||||
$this->submitForm([], '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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if file field tracks file usages correctly on translated nodes.
|
||||
*/
|
||||
public function testFileUsage(): void {
|
||||
/** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
|
||||
$file_usage = \Drupal::service('file.usage');
|
||||
|
||||
// Create a node and upload a file.
|
||||
$node = $this->drupalCreateNode(['type' => 'page']);
|
||||
$edit = [
|
||||
'files[' . $this->fieldName . '_0]' => \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[0]->uri),
|
||||
];
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check if the file usage is correct.
|
||||
$file = File::load($this->getLastFileId());
|
||||
$this->assertEquals($file_usage->listUsage($file), ['file' => ['node' => [$node->id() => '1']]]);
|
||||
|
||||
// Check if the file usage is tracked correctly when changing the original
|
||||
// language of an entity.
|
||||
$node = Node::load($node->id());
|
||||
$node->set('langcode', 'fr');
|
||||
$node->save();
|
||||
$this->assertEquals($file_usage->listUsage($file), ['file' => ['node' => [$node->id() => '1']]]);
|
||||
}
|
||||
|
||||
}
|
||||
249
web/core/modules/file/tests/src/Functional/FilePrivateTest.php
Normal file
249
web/core/modules/file/tests/src/Functional/FilePrivateTest.php
Normal file
@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\node\Traits\NodeAccessTrait;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Uploads a test to a private node and checks access.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FilePrivateTest extends FileFieldTestBase {
|
||||
|
||||
use NodeAccessTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node_access_test', 'field_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->addPrivateField(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(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$type_name = 'article';
|
||||
$field_name = $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]);
|
||||
/** @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->assertSession()->responseContains($node_file->getFilename());
|
||||
// Ensure the file can be downloaded.
|
||||
$this->drupalGet($node_file->createFileUrl(FALSE));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->drupalLogOut();
|
||||
// Ensure the file cannot be downloaded after logging out.
|
||||
$this->drupalGet($node_file->createFileUrl(FALSE));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// 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]);
|
||||
$node = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$no_access_field_name}->target_id);
|
||||
|
||||
// Ensure the file cannot be downloaded.
|
||||
$file_url = $node_file->createFileUrl(FALSE);
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Attempt to reuse the file when editing a node.
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$this->drupalGet('node/add/' . $type_name);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
|
||||
// Can't use submitForm() 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('Save');
|
||||
$this->assertSession()->addressEquals('node/' . $new_node->id());
|
||||
// Make sure the submitted hidden file field is empty.
|
||||
$new_node = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($new_node->id());
|
||||
$this->assertTrue($new_node->get($field_name)->isEmpty());
|
||||
// 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 submitForm() 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('Save');
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$this->assertSession()->addressEquals('node/' . $new_node->id());
|
||||
// Make sure the submitted hidden file field is empty.
|
||||
$new_node = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($new_node->id());
|
||||
$this->assertTrue($new_node->get($field_name)->isEmpty());
|
||||
|
||||
// Now make file_test_file_download() return everything.
|
||||
\Drupal::state()->set('file_test.allow_all', TRUE);
|
||||
// Delete the node.
|
||||
$node->delete();
|
||||
// Ensure the temporary file can still be downloaded by the owner.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Ensure the temporary file cannot be downloaded by an anonymous user.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Ensure the temporary file cannot be downloaded by another user.
|
||||
$account = $this->drupalCreateUser();
|
||||
$this->drupalLogin($account);
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// 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->submitForm($edit, 'Upload');
|
||||
/** @var \Drupal\file\FileStorageInterface $file_storage */
|
||||
$file_storage = $this->container->get('entity_type.manager')->getStorage('file');
|
||||
$files = $file_storage->loadByProperties(['uid' => 0]);
|
||||
$this->assertCount(1, $files, 'Loaded one anonymous file.');
|
||||
$file = end($files);
|
||||
$this->assertTrue($file->isTemporary(), 'File is temporary.');
|
||||
$usage = $this->container->get('file.usage')->listUsage($file);
|
||||
$this->assertEmpty($usage, 'No file usage found.');
|
||||
$file_url = $file->createFileUrl(FALSE);
|
||||
// Ensure the anonymous uploader has access to the temporary file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->getSession()->reset();
|
||||
// Ensure that a different anonymous user cannot access the temporary file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// 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->submitForm($edit, '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->assertEmpty($usage, 'No file usage found.');
|
||||
$file_url = $file->createFileUrl(FALSE);
|
||||
// Ensure the anonymous uploader has access to the temporary file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->getSession()->reset();
|
||||
// Ensure that a different anonymous user cannot access the temporary file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// 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->submitForm($edit, '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->assertCount(1, $usage, 'File usage found.');
|
||||
$file_url = $file->createFileUrl(FALSE);
|
||||
// Ensure the anonymous uploader has access to the file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->getSession()->reset();
|
||||
// Ensure that a different anonymous user can access the file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// 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->submitForm($edit, '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->assertCount(1, $usage, 'File usage found.');
|
||||
$file_url = $file->createFileUrl(FALSE);
|
||||
// Ensure the anonymous uploader cannot access to the file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->getSession()->reset();
|
||||
// Ensure that a different anonymous user cannot access the temporary file.
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\StringTranslation\ByteSizeMarkup;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* Tests file token replacement.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileTokenReplaceTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Creates a file, then tests the tokens generated from it.
|
||||
*/
|
||||
public function testFileTokenReplacement(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$token_service = \Drupal::token();
|
||||
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
|
||||
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
|
||||
$date_formatter = $this->container->get('date.formatter');
|
||||
|
||||
// Create file field.
|
||||
$type_name = 'article';
|
||||
$field_name = 'field_' . $this->randomMachineName();
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
|
||||
$test_file = $this->getTestFile('text');
|
||||
// Coping a file to test uploads with non-latin filenames.
|
||||
// cSpell:disable-next-line
|
||||
$filename = \Drupal::service('file_system')->dirname($test_file->getFileUri()) . '/текстовый файл.txt';
|
||||
$test_file = \Drupal::service('file.repository')->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 = $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:uuid]'] = $file->uuid();
|
||||
$tests['[file:name]'] = Html::escape($file->getFilename());
|
||||
$tests['[file:path]'] = Html::escape($file->getFileUri());
|
||||
$tests['[file:mime]'] = Html::escape($file->getMimeType());
|
||||
$tests['[file:size]'] = ByteSizeMarkup::create($file->getSize());
|
||||
$tests['[file:url]'] = Html::escape($file->createFileUrl(FALSE));
|
||||
$tests['[file:created]'] = $date_formatter->format($file->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
|
||||
$tests['[file:created:short]'] = $date_formatter->format($file->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
|
||||
$tests['[file:changed]'] = $date_formatter->format($file->getChangedTime(), 'medium', '', NULL, $language_interface->getId());
|
||||
$tests['[file:changed:short]'] = $date_formatter->format($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:uuid]'] = $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->assertNotContains(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->assertEquals($expected, $output, "Sanitized file token $input replaced.");
|
||||
$this->assertEquals($metadata_tests[$input], $bubbleable_metadata);
|
||||
}
|
||||
|
||||
// Generate and test unsanitized tokens.
|
||||
$tests['[file:name]'] = $file->getFilename();
|
||||
$tests['[file:path]'] = $file->getFileUri();
|
||||
$tests['[file:mime]'] = $file->getMimeType();
|
||||
$tests['[file:size]'] = ByteSizeMarkup::create($file->getSize());
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $token_service->replace($input, ['file' => $file], ['langcode' => $language_interface->getId(), 'sanitize' => FALSE]);
|
||||
$this->assertEquals($expected, $output, "Unsanitized file token $input replaced.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* Entity type ID for this storage.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static string $entityTypeId;
|
||||
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* @covers ::viewElements
|
||||
*
|
||||
* @dataProvider dataProvider
|
||||
*/
|
||||
public function testRender($tag_count, $formatter_settings): void {
|
||||
// Create a file field that accepts .mp3 and an unknown file extension.
|
||||
$field_config = $this->createMediaField('file_audio', 'unknown-extension, 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 = $file1->createFileUrl();
|
||||
$file2_url = $file2->createFileUrl();
|
||||
|
||||
$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']");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
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 = $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();
|
||||
|
||||
$this->container->get('entity_display.repository')
|
||||
->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->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 static function dataProvider(): array {
|
||||
return [
|
||||
[2, []],
|
||||
[1, ['multiple_file_display_type' => 'sources']],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* @covers ::viewElements
|
||||
*
|
||||
* @dataProvider dataProvider
|
||||
*/
|
||||
public function testRender($tag_count, $formatter_settings): void {
|
||||
$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 = $file1->createFileUrl();
|
||||
$file2_url = $file2->createFileUrl();
|
||||
|
||||
$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']");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the attributes added to the formatter are applied on render.
|
||||
*/
|
||||
public function testAttributes(): void {
|
||||
$field_config = $this->createMediaField(
|
||||
'file_video',
|
||||
'mp4',
|
||||
[
|
||||
'autoplay' => TRUE,
|
||||
'loop' => TRUE,
|
||||
'muted' => TRUE,
|
||||
'playsinline' => TRUE,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
]
|
||||
);
|
||||
|
||||
file_put_contents('public://file.mp4', str_repeat('t', 10));
|
||||
$file = File::create([
|
||||
'uri' => 'public://file.mp4',
|
||||
'filename' => 'file.mp4',
|
||||
]);
|
||||
$file->save();
|
||||
|
||||
$entity = EntityTest::create([
|
||||
$field_config->getName() => [
|
||||
[
|
||||
'target_id' => $file->id(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl());
|
||||
|
||||
$file_url = \Drupal::service('file_url_generator')->generateString($file->getFileUri());
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
$assert_session->elementExists('css', "video[autoplay='autoplay'] > source[src='$file_url'][type='video/mp4']");
|
||||
$assert_session->elementExists('css', "video[loop='loop'] > source[src='$file_url'][type='video/mp4']");
|
||||
$assert_session->elementExists('css', "video[muted='muted'] > source[src='$file_url'][type='video/mp4']");
|
||||
$assert_session->elementExists('css', "video[playsinline='playsinline'] > source[src='$file_url'][type='video/mp4']");
|
||||
$assert_session->elementExists('css', "video[width='800'] > source[src='$file_url'][type='video/mp4']");
|
||||
$assert_session->elementExists('css', "video[height='600'] > source[src='$file_url'][type='video/mp4']");
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $displayRepository */
|
||||
$displayRepository = $this->container->get('entity_display.repository');
|
||||
$entityDisplay = $displayRepository->getViewDisplay('entity_test', 'entity_test', 'full');
|
||||
$fieldName = $field_config->get('field_name');
|
||||
$fieldDisplay = $entityDisplay->getComponent($fieldName);
|
||||
|
||||
// Tests only setting width.
|
||||
$fieldDisplay['settings']['height'] = NULL;
|
||||
$entityDisplay->setComponent($fieldName, $fieldDisplay);
|
||||
$entityDisplay->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl());
|
||||
$assert_session->elementAttributeNotExists('css', 'video', 'height');
|
||||
|
||||
// Tests only setting height.
|
||||
$fieldDisplay['settings']['height'] = 600;
|
||||
$fieldDisplay['settings']['width'] = NULL;
|
||||
$entityDisplay->setComponent($fieldName, $fieldDisplay);
|
||||
$entityDisplay->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl());
|
||||
$assert_session->elementAttributeNotExists('css', 'video', 'width');
|
||||
|
||||
// Tests both height and width empty.
|
||||
$fieldDisplay['settings']['height'] = NULL;
|
||||
$fieldDisplay['settings']['width'] = NULL;
|
||||
$entityDisplay->setComponent($fieldName, $fieldDisplay);
|
||||
$entityDisplay->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl());
|
||||
$assert_session->elementAttributeNotExists('css', 'video', 'height');
|
||||
$assert_session->elementAttributeNotExists('css', 'video', 'width');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Formatter;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for video formatters.
|
||||
*
|
||||
* @see file_post_update_add_playsinline()
|
||||
*
|
||||
* @group Update
|
||||
* @group legacy
|
||||
*/
|
||||
class FileVideoFormatterUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file', 'media'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles(): void {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../fixtures/update/file_post_update_playsinline-3046152.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \file_post_update_add_playsinline
|
||||
*/
|
||||
public function testPlaysInlineUpdate(): void {
|
||||
$display = $this->config('core.entity_view_display.node.article.default');
|
||||
|
||||
$settings = $display->get('content.field_video.settings');
|
||||
$this->assertArrayNotHasKey('playsinline', $settings);
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
$display = $this->config('core.entity_view_display.node.article.default');
|
||||
$settings = $display->get('content.field_video.settings');
|
||||
|
||||
$this->assertFalse($settings['playsinline']);
|
||||
}
|
||||
|
||||
}
|
||||
14
web/core/modules/file/tests/src/Functional/GenericTest.php
Normal file
14
web/core/modules/file/tests/src/Functional/GenericTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for file.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests multiple file upload.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class MultipleFileUploadTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$admin = $this->drupalCreateUser(['administer themes']);
|
||||
$this->drupalLogin($admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multiple file field with all file extensions.
|
||||
*/
|
||||
public function testMultipleFileFieldWithAllFileExtensions(): void {
|
||||
$theme = 'test_theme_settings';
|
||||
\Drupal::service('theme_installer')->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->assertStringNotContainsString('Only files with the following extensions are allowed', $page->getContent());
|
||||
$this->assertStringContainsString('The configuration options have been saved.', $page->getContent());
|
||||
$this->assertStringContainsString('file1.wtf', $page->getContent());
|
||||
$this->assertStringContainsString('file2.wtf', $page->getContent());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* Uploads private files to translated node and checks access.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class PrivateFileOnTranslatedEntityTest extends FileFieldTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the file field used in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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 = $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.
|
||||
static::createLanguageFromLangcode('fr');
|
||||
|
||||
// Enable translation for "Basic page" nodes.
|
||||
static::enableContentTranslation('node', 'page');
|
||||
static::setFieldTranslatable('node', 'page', $this->fieldName, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests private file fields on translated nodes.
|
||||
*/
|
||||
public function testPrivateLanguageFile(): void {
|
||||
// Verify that the file field on the "Basic page" node type is translatable.
|
||||
$definitions = \Drupal::service('entity_field.manager')->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.
|
||||
$file = File::create(
|
||||
[
|
||||
'uri' => $this->drupalGetTestFiles('text')[0]->uri,
|
||||
]
|
||||
);
|
||||
$file->save();
|
||||
|
||||
$default_language_node->set($this->fieldName, $file->id());
|
||||
$default_language_node->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.
|
||||
$node = Node::load($default_language_node->id());
|
||||
$node_file = File::load($node->{$this->fieldName}->target_id);
|
||||
$this->drupalGet($node_file->createFileUrl(FALSE));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Translate the node into French.
|
||||
$node->addTranslation(
|
||||
'fr', [
|
||||
'title' => $this->randomString(),
|
||||
]
|
||||
);
|
||||
$node->save();
|
||||
|
||||
// Remove the existing file.
|
||||
$existing_file = $node->{$this->fieldName}->entity;
|
||||
if ($existing_file) {
|
||||
$node->set($this->fieldName, NULL);
|
||||
$existing_file->delete();
|
||||
$node->save();
|
||||
}
|
||||
|
||||
// Upload a different file.
|
||||
$default_language_node = $node->getTranslation('fr');
|
||||
$file = File::create(
|
||||
[
|
||||
'uri' => $this->drupalGetTestFiles('text')[1]->uri,
|
||||
]
|
||||
);
|
||||
$file->save();
|
||||
$default_language_node->set($this->fieldName, $file->id());
|
||||
$default_language_node->save();
|
||||
$last_fid = $this->getLastFileId();
|
||||
|
||||
// Verify the translation was created.
|
||||
$default_language_node = Node::load($default_language_node->id());
|
||||
$this->assertTrue($default_language_node->hasTranslation('fr'), 'Node found in database.');
|
||||
// Verify that the new file got saved.
|
||||
$this->assertGreaterThan($last_fid_prior, $last_fid);
|
||||
|
||||
// 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($node_file->createFileUrl(FALSE));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
/**
|
||||
* Tests the file uploading functions.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class RemoteFileSaveUploadTest extends SaveUploadTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FileJsonBasicAuthTest extends FileResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Rest;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use PHPUnit\Framework\Attributes\Before;
|
||||
|
||||
/**
|
||||
* Resource test base for file entity.
|
||||
*/
|
||||
abstract class FileResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected 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;
|
||||
|
||||
/**
|
||||
* Marks some tests as skipped because XML cannot be deserialized.
|
||||
*/
|
||||
#[Before]
|
||||
public function fileResourceTestBaseSkipTests(): void {
|
||||
if ($this->name() === '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('Drupal does not allow creating file entities independently.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access content']);
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
// \Drupal\file\FileAccessControlHandler::checkAccess() grants 'update'
|
||||
// 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();
|
||||
return;
|
||||
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['delete any file']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->setPermanent();
|
||||
$file->save();
|
||||
|
||||
file_put_contents($file->getFileUri(), 'Drupal');
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'changed' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getCreatedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'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 [
|
||||
'url.site',
|
||||
'user.permissions',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
return match($method) {
|
||||
'GET' => "The 'access content' permission is required.",
|
||||
'PATCH' => "Only the file owner can update the file entity.",
|
||||
'DELETE' => "The 'delete any file' permission is required.",
|
||||
default => parent::getExpectedUnauthorizedAccessMessage($method),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,598 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['dblog', 'file_validator_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An image file path for uploading.
|
||||
*
|
||||
* @var \Drupal\file\FileInterface
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* A PHP file path for upload security testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $phpFile;
|
||||
|
||||
/**
|
||||
* The largest file id when the test starts.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxFidBefore;
|
||||
|
||||
/**
|
||||
* Extension of the image filename.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $imageExtension;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$account = $this->drupalCreateUser(['access site reports']);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$image_files = $this->drupalGetTestFiles('image');
|
||||
$this->image = File::create((array) current($image_files));
|
||||
|
||||
[, $this->imageExtension] = explode('.', $this->image->getFilename());
|
||||
$this->assertFileExists($this->image->getFileUri());
|
||||
|
||||
$this->phpFile = current($this->drupalGetTestFiles('php'));
|
||||
$this->assertFileExists($this->phpFile->uri);
|
||||
|
||||
$this->maxFidBefore = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
|
||||
/** @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' => FileExists::Replace->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called then clean out the hook
|
||||
// counters.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
FileTestHelper::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the _file_save_upload_from_form() function.
|
||||
*/
|
||||
public function testNormal(): void {
|
||||
$max_fid_after = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
// Verify that a new file was created.
|
||||
$this->assertGreaterThan($this->maxFidBefore, $max_fid_after);
|
||||
$file1 = File::load($max_fid_after);
|
||||
$this->assertInstanceOf(File::class, $file1);
|
||||
// MIME type of the uploaded image may be either image/jpeg or image/png.
|
||||
$this->assertEquals('image', substr($file1->getMimeType(), 0, 5), 'A MIME type was set.');
|
||||
|
||||
// Reset the hook counters to get rid of the 'load' we just called.
|
||||
FileTestHelper::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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
$max_fid_after = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
$file2 = File::load($max_fid_after);
|
||||
$this->assertInstanceOf(File::class, $file2);
|
||||
// MIME type of the uploaded image may be either image/jpeg or image/png.
|
||||
$this->assertEquals('image', substr($file2->getMimeType(), 0, 5), '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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
$this->assertFileExists('temporary://' . $dir . '/' . trim(\Drupal::service('file_system')->basename($image3_realpath)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests extension handling.
|
||||
*/
|
||||
public function testHandleExtension(): void {
|
||||
/** @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' => FileExists::Replace->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
'extensions' => $extensions,
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('Only files with the following extensions are allowed: <em class="placeholder">' . $extensions . '</em>');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$extensions = 'foo ' . $this->imageExtension;
|
||||
// Now tell _file_save_upload_from_form() to allow the extension of our test
|
||||
// image.
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
'extensions' => $extensions,
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('Only files with the following extensions are allowed:');
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'load', 'update']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Now tell _file_save_upload_from_form() to allow any extension.
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
];
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('Only files with the following extensions are allowed:');
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'load', 'update']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests dangerous file handling.
|
||||
*/
|
||||
public function testHandleDangerousFile(): void {
|
||||
$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' => FileExists::Replace->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->phpFile->uri),
|
||||
'is_image_file' => FALSE,
|
||||
'extensions' => 'php txt',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('For security reasons, your upload has been renamed to <em class="placeholder">' . $this->phpFile->filename . '_.txt</em>');
|
||||
$this->assertSession()->pageTextContains('File MIME type is text/plain.');
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// 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.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is {$this->phpFile->filename}");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Turn off insecure uploads.
|
||||
$config->set('allow_insecure_uploads', 0)->save();
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->phpFile->uri),
|
||||
'is_image_file' => FALSE,
|
||||
'extensions' => 'php',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been rejected.');
|
||||
$this->assertSession()->pageTextContains('Epic upload FAIL!');
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file munge handling.
|
||||
*/
|
||||
public function testHandleFileMunge(): void {
|
||||
/** @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();
|
||||
$original_uri = $this->image->getFileUri();
|
||||
/** @var \Drupal\file\FileRepositoryInterface $file_repository */
|
||||
$file_repository = \Drupal::service('file.repository');
|
||||
$this->image = $file_repository->move($this->image, $original_uri . '.foo.' . $this->imageExtension);
|
||||
|
||||
// Reset the hook counters to get rid of the 'move' we just called.
|
||||
FileTestHelper::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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is {$munged_filename}");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Test with uppercase extensions.
|
||||
$this->image = $file_repository->move($this->image, $original_uri . '.foo2.' . $this->imageExtension);
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
$extensions = $this->imageExtension;
|
||||
$edit = [
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
'extensions' => mb_strtoupper($extensions),
|
||||
];
|
||||
|
||||
$munged_filename = $this->image->getFilename();
|
||||
$munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.'));
|
||||
$munged_filename .= '_.' . $this->imageExtension;
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is {$munged_filename}");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// 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.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Ensure we don't munge files if we're allowing any extension.
|
||||
$edit = [
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is {$this->image->getFilename()}");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Ensure that setting $validators['FileExtension'] = ['extensions' => NULL]
|
||||
// rejects all files.
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$edit = [
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_string',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests renaming when uploading over a file that already exists.
|
||||
*/
|
||||
public function testExistingRename(): void {
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Rename->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests replacement when uploading over a file that already exists.
|
||||
*/
|
||||
public function testExistingReplace(): void {
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// 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(): void {
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Error->name,
|
||||
'files[file_test_upload][]' => $file_system->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the no hooks were called while failing.
|
||||
$this->assertFileHooksCalled([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for no failures when not uploading a file.
|
||||
*/
|
||||
public function testNoUpload(): void {
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm([], 'Submit');
|
||||
$this->assertSession()->pageTextNotContains("Epic upload FAIL!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for log entry on failing destination.
|
||||
*/
|
||||
public function testDrupalMovingUploadedFileError(): void {
|
||||
// Create a directory and make it not writable.
|
||||
$test_directory = 'test_drupal_move_uploaded_file_fail';
|
||||
\Drupal::service('file_system')->mkdir('temporary://' . $test_directory, 0000);
|
||||
$this->assertDirectoryExists('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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('File upload error. Could not move uploaded file.');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Uploading failed. Now check the log.
|
||||
$this->drupalGet('admin/reports/dblog');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// The full log message is in the title attribute of the link, so we cannot
|
||||
// use ::pageTextContains() here.
|
||||
$destination = 'temporary://' . $test_directory . '/' . $this->image->getFilename();
|
||||
$this->assertSession()->responseContains("Upload error. Could not move uploaded file {$this->image->getFilename()} to destination {$destination}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that form validation does not change error messages.
|
||||
*/
|
||||
public function testErrorMessagesAreNotChanged(): void {
|
||||
$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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Ensure the expected error message is present and the counts before and
|
||||
// after calling _file_save_upload_from_form() are correct.
|
||||
$this->assertSession()->pageTextContains($error);
|
||||
$this->assertSession()->pageTextContains('Number of error messages before _file_save_upload_from_form(): 1');
|
||||
$this->assertSession()->pageTextContains('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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Ensure the expected error message is present and the counts before and
|
||||
// after calling _file_save_upload_from_form() are correct.
|
||||
$this->assertSession()->pageTextContains($error);
|
||||
$this->assertSession()->pageTextContains('Number of error messages before _file_save_upload_from_form(): 1');
|
||||
$this->assertSession()->pageTextContains('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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Ensure the error message is not present and the counts before and after
|
||||
// calling _file_save_upload_from_form() are correct.
|
||||
$this->assertSession()->pageTextNotContains($error);
|
||||
$this->assertSession()->pageTextContains('Number of error messages before _file_save_upload_from_form(): 0');
|
||||
$this->assertSession()->pageTextContains('Number of error messages after _file_save_upload_from_form(): 0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that multiple validation errors are combined in one message.
|
||||
*/
|
||||
public function testCombinedErrorMessages(): void {
|
||||
$text_file = current($this->drupalGetTestFiles('text'));
|
||||
$this->assertFileExists($text_file->uri);
|
||||
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
|
||||
// Can't use submitForm() 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 = [
|
||||
'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($text_file->uri);
|
||||
$client->request($form->getMethod(), $form->getUri(), $edit, $files);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Search for combined error message followed by a formatted list of
|
||||
// messages.
|
||||
$this->assertSession()->responseContains('One or more files could not be uploaded.<ul>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests highlighting of file upload field when it has an error.
|
||||
*/
|
||||
public function testUploadFieldIsHighlighted(): void {
|
||||
$this->assertCount(0, $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->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
$this->assertCount(1, $this->cssSelect('input[name="files[file_test_upload][]"].error'), 'File upload field has error.');
|
||||
}
|
||||
|
||||
}
|
||||
900
web/core/modules/file/tests/src/Functional/SaveUploadTest.php
Normal file
900
web/core/modules/file/tests/src/Functional/SaveUploadTest.php
Normal file
@ -0,0 +1,900 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
// cSpell:ignore TÉXT Pácê
|
||||
|
||||
/**
|
||||
* Tests the file_save_upload() function.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class SaveUploadTest extends FileManagedTestBase {
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['dblog', 'file_validator_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An image file path for uploading.
|
||||
*
|
||||
* @var \Drupal\file\FileInterface
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* A PHP file path for upload security testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $phpFile;
|
||||
|
||||
/**
|
||||
* The largest file id when the test starts.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxFidBefore;
|
||||
|
||||
/**
|
||||
* Extension of the image filename.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $imageExtension;
|
||||
|
||||
/**
|
||||
* The user used by the test.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->account = $this->drupalCreateUser(['access site reports']);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
$image_files = $this->drupalGetTestFiles('image');
|
||||
$this->image = File::create((array) current($image_files));
|
||||
|
||||
[, $this->imageExtension] = explode('.', $this->image->getFilename());
|
||||
$this->assertFileExists($this->image->getFileUri());
|
||||
|
||||
$this->phpFile = current($this->drupalGetTestFiles('php'));
|
||||
$this->assertFileExists($this->phpFile->uri);
|
||||
|
||||
$this->maxFidBefore = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
|
||||
// Upload with replace to guarantee there's something there.
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Check that the success message is present.
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called then clean out the hook
|
||||
// counters.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
FileTestHelper::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the file_save_upload() function.
|
||||
*/
|
||||
public function testNormal(): void {
|
||||
$max_fid_after = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
// Verify that a new file was created.
|
||||
$this->assertGreaterThan($this->maxFidBefore, $max_fid_after);
|
||||
$file1 = File::load($max_fid_after);
|
||||
$this->assertInstanceOf(File::class, $file1);
|
||||
// MIME type of the uploaded image may be either image/jpeg or image/png.
|
||||
$this->assertEquals('image', substr($file1->getMimeType(), 0, 5), 'A MIME type was set.');
|
||||
|
||||
// Reset the hook counters to get rid of the 'load' we just called.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Upload a second file.
|
||||
$image2 = current($this->drupalGetTestFiles('image'));
|
||||
$edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image2->uri)];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
$max_fid_after = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
$file2 = File::load($max_fid_after);
|
||||
$this->assertInstanceOf(File::class, $file2);
|
||||
// MIME type of the uploaded image may be either image/jpeg or image/png.
|
||||
$this->assertEquals('image', substr($file2->getMimeType(), 0, 5), '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->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
$this->assertFileExists('temporary://' . $dir . '/' . trim(\Drupal::service('file_system')->basename($image3_realpath)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uploading a duplicate file.
|
||||
*/
|
||||
public function testDuplicate(): void {
|
||||
// It should not be possible to create two managed files with the same URI.
|
||||
$image1 = current($this->drupalGetTestFiles('image'));
|
||||
$edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image1->uri)];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$max_fid_after = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
$file1 = File::load($max_fid_after);
|
||||
|
||||
// Simulate a race condition where two files are uploaded at almost the same
|
||||
// time, by removing the first uploaded file from disk (leaving the entry in
|
||||
// the file_managed table) before trying to upload another file with the
|
||||
// same name.
|
||||
unlink(\Drupal::service('file_system')->realpath($file1->getFileUri()));
|
||||
|
||||
$image2 = $image1;
|
||||
$edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image2->uri)];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
// Received a 200 response for posted test file.
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("The file {$file1->getFileUri()} already exists. Enter a unique file URI.");
|
||||
$max_fid_before_duplicate = $max_fid_after;
|
||||
$max_fid_after = (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
$this->assertEquals($max_fid_before_duplicate, $max_fid_after, 'A new managed file was not created.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests extension handling.
|
||||
*/
|
||||
public function testHandleExtension(): void {
|
||||
// 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' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'extensions' => $extensions,
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('Only files with the following extensions are allowed: <em class="placeholder">' . $extensions . '</em>');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$extensions = 'foo ' . $this->imageExtension;
|
||||
// Now tell file_save_upload() to allow the extension of our test image.
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'extensions' => $extensions,
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains("Only files with the following extensions are allowed:");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'load', 'update']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Now tell file_save_upload() to allow any extension.
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains("Only files with the following extensions are allowed:");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'load', 'update']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Now tell file_save_upload() to allow any extension and try and upload a
|
||||
// malicious file.
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->phpFile->uri),
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
'is_image_file' => FALSE,
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('For security reasons, your upload has been renamed to <em class="placeholder">' . $this->phpFile->filename . '_.txt</em>');
|
||||
$this->assertSession()->pageTextContains('File name is php-2.php_.txt.');
|
||||
$this->assertSession()->pageTextContains('File MIME type is text/plain.');
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests dangerous file handling.
|
||||
*/
|
||||
public function testHandleDangerousFile(): void {
|
||||
$config = $this->config('system.file');
|
||||
// Allow the .php extension and make sure it gets munged and given a .txt
|
||||
// extension for safety. Also check to make sure its MIME type was changed.
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->phpFile->uri),
|
||||
'is_image_file' => FALSE,
|
||||
'extensions' => 'php txt',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('For security reasons, your upload has been renamed to <em class="placeholder">' . $this->phpFile->filename . '_.txt</em>');
|
||||
$this->assertSession()->pageTextContains('File name is php-2.php_.txt.');
|
||||
$this->assertSession()->pageTextContains('File MIME type is text/plain.');
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// 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.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains('File name is php-2.php.');
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Even with insecure uploads allowed, the .php file should not be uploaded
|
||||
// if it is not explicitly included in the list of allowed extensions.
|
||||
$edit['extensions'] = 'foo';
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('Only files with the following extensions are allowed: <em class="placeholder">' . $edit['extensions'] . '</em>');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Turn off insecure uploads, then try the same thing as above (ensure that
|
||||
// the .php file is still rejected since it's not in the list of allowed
|
||||
// extensions).
|
||||
$config->set('allow_insecure_uploads', 0)->save();
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('Only files with the following extensions are allowed: <em class="placeholder">' . $edit['extensions'] . '</em>');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
\Drupal::service('cache.config')->deleteAll();
|
||||
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->phpFile->uri),
|
||||
'is_image_file' => FALSE,
|
||||
'extensions' => 'php',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been rejected.');
|
||||
$this->assertSession()->pageTextContains('Epic upload FAIL!');
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test dangerous file handling.
|
||||
*/
|
||||
public function testHandleDotFile(): void {
|
||||
$dot_file = $this->siteDirectory . '/.test';
|
||||
file_put_contents($dot_file, 'This is a test');
|
||||
$config = $this->config('system.file');
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($dot_file),
|
||||
'is_image_file' => FALSE,
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('The specified file .test could not be uploaded');
|
||||
$this->assertSession()->pageTextContains('Epic upload FAIL!');
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Rename->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($dot_file),
|
||||
'is_image_file' => FALSE,
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed to test.');
|
||||
$this->assertSession()->pageTextContains('File name is test.');
|
||||
$this->assertSession()->pageTextContains('You WIN!');
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Turn off insecure uploads, then try the same thing as above to ensure dot
|
||||
// files are renamed regardless.
|
||||
$config->set('allow_insecure_uploads', 0)->save();
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed to test_0.');
|
||||
$this->assertSession()->pageTextContains('File name is test_0.');
|
||||
$this->assertSession()->pageTextContains('You WIN!');
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file munge handling.
|
||||
*/
|
||||
public function testHandleFileMunge(): void {
|
||||
// Ensure insecure uploads are disabled for this test.
|
||||
$this->config('system.file')->set('allow_insecure_uploads', 0)->save();
|
||||
$original_image_uri = $this->image->getFileUri();
|
||||
/** @var \Drupal\file\FileRepositoryInterface $file_repository */
|
||||
$file_repository = \Drupal::service('file.repository');
|
||||
$this->image = $file_repository->move($this->image, $original_image_uri . '.foo.' . $this->imageExtension);
|
||||
|
||||
// Reset the hook counters to get rid of the 'move' we just called.
|
||||
FileTestHelper::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->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is $munged_filename");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Ensure we don't munge the .foo extension if it is in the list of allowed
|
||||
// extensions.
|
||||
$extensions = 'foo ' . $this->imageExtension;
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'extensions' => $extensions,
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is {$this->image->getFilename()}");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Ensure we don't munge files if we're allowing any extension.
|
||||
$this->image = $file_repository->move($this->image, $original_image_uri . '.foo.txt.' . $this->imageExtension);
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is {$this->image->getFilename()}");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Test that a dangerous extension such as .php is munged even if it is in
|
||||
// the list of allowed extensions.
|
||||
$this->image = $file_repository->move($this->image, $original_image_uri . '.php.' . $this->imageExtension);
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
$extensions = 'php ' . $this->imageExtension;
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'extensions' => $extensions,
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is image-test.png_.php_.png");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Dangerous extensions are munged even when all extensions are allowed.
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is image-test.png_.php__0.png");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Dangerous extensions are munged if is renamed to end in .txt.
|
||||
$this->image = $file_repository->move($this->image, $original_image_uri . '.cgi.' . $this->imageExtension . '.txt');
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Dangerous extensions are munged even when all extensions are allowed.
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_array',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("File name is image-test.png_.cgi_.png_.txt");
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
|
||||
// Reset the hook counters.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Ensure that setting $validators['FileExtension'] = ['extensions' = '']
|
||||
// rejects all files without munging or renaming.
|
||||
$edit = [
|
||||
'files[file_test_upload][]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
'allow_all_extensions' => 'empty_string',
|
||||
];
|
||||
|
||||
$this->drupalGet('file-test/save_upload_from_form_test');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('For security reasons, your upload has been renamed');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests renaming when uploading over a file that already exists.
|
||||
*/
|
||||
public function testExistingRename(): void {
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Rename->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
$this->assertSession()->pageTextContains('File name is image-test_0.png.');
|
||||
|
||||
// Check that the correct hooks were called.
|
||||
$this->assertFileHooksCalled(['validate', 'insert']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests replacement when uploading over a file that already exists.
|
||||
*/
|
||||
public function testExistingReplace(): void {
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Replace->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("You WIN!");
|
||||
$this->assertSession()->pageTextContains('File name is image-test.png.');
|
||||
|
||||
// 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(): void {
|
||||
$edit = [
|
||||
'file_test_replace' => FileExists::Error->name,
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($this->image->getFileUri()),
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Check that the no hooks were called while failing.
|
||||
$this->assertFileHooksCalled([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for no failures when not uploading a file.
|
||||
*/
|
||||
public function testNoUpload(): void {
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm([], 'Submit');
|
||||
$this->assertSession()->pageTextNotContains("Epic upload FAIL!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for log entry on failing destination.
|
||||
*/
|
||||
public function testDrupalMovingUploadedFileError(): void {
|
||||
// Create a directory and make it not writable.
|
||||
$test_directory = 'test_drupal_move_uploaded_file_fail';
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$file_system->mkdir('temporary://' . $test_directory, 0000);
|
||||
$this->assertDirectoryExists('temporary://' . $test_directory);
|
||||
|
||||
$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->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('File upload error. Could not move uploaded file.');
|
||||
$this->assertSession()->pageTextContains("Epic upload FAIL!");
|
||||
|
||||
// Uploading failed. Now check the log.
|
||||
$this->drupalGet('admin/reports/dblog');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// The full log message is in the title attribute of the link, so we cannot
|
||||
// use ::pageTextContains() here.
|
||||
$destination = 'temporary://' . $test_directory . '/' . $this->image->getFilename();
|
||||
$this->assertSession()->responseContains("Upload error. Could not move uploaded file {$this->image->getFilename()} to destination {$destination}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that filenames containing invalid UTF-8 are rejected.
|
||||
*/
|
||||
public function testInvalidUtf8FilenameUpload(): void {
|
||||
$this->drupalGet('file-test/upload');
|
||||
|
||||
// Filename containing invalid UTF-8.
|
||||
$filename = "x\xc0xx.gif";
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$data = [
|
||||
'multipart' => [
|
||||
[
|
||||
'name' => 'file_test_replace',
|
||||
'contents' => FileExists::Rename->name,
|
||||
],
|
||||
[
|
||||
'name' => 'form_id',
|
||||
'contents' => '_file_test_form',
|
||||
],
|
||||
[
|
||||
'name' => 'form_build_id',
|
||||
'contents' => $page->find('hidden_field_selector', ['hidden_field', 'form_build_id'])->getAttribute('value'),
|
||||
],
|
||||
[
|
||||
'name' => 'form_token',
|
||||
'contents' => $page->find('hidden_field_selector', ['hidden_field', 'form_token'])->getAttribute('value'),
|
||||
],
|
||||
[
|
||||
'name' => 'op',
|
||||
'contents' => 'Submit',
|
||||
],
|
||||
[
|
||||
'name' => 'files[file_test_upload]',
|
||||
'contents' => 'Test content',
|
||||
'filename' => $filename,
|
||||
],
|
||||
],
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'http_errors' => FALSE,
|
||||
];
|
||||
|
||||
$this->assertFileDoesNotExist('temporary://' . $filename);
|
||||
// Use Guzzle's HTTP client directly so we can POST files without having to
|
||||
// write them to disk. Not all filesystem support writing files with invalid
|
||||
// UTF-8 filenames.
|
||||
$response = $this->getHttpClient()->request('POST', Url::fromUri('base:file-test/upload')->setAbsolute()->toString(), $data);
|
||||
|
||||
$content = (string) $response->getBody();
|
||||
$this->htmlOutput($content);
|
||||
$error_text = 'The file <em class="placeholder">' . Html::escape($filename) . '</em> could not be uploaded because the name is invalid.';
|
||||
$this->assertStringContainsString($error_text, $content);
|
||||
$this->assertStringContainsString('Epic upload FAIL!', $content);
|
||||
$this->assertFileDoesNotExist('temporary://' . $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the file_save_upload() function when the field is required.
|
||||
*/
|
||||
public function testRequired(): void {
|
||||
// Reset the hook counters to get rid of the 'load' we just called.
|
||||
FileTestHelper::reset();
|
||||
|
||||
// Confirm the field is required.
|
||||
$this->drupalGet('file-test/upload_required');
|
||||
$this->submitForm([], 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('field is required');
|
||||
|
||||
// Confirm that uploading another file works.
|
||||
$image = current($this->drupalGetTestFiles('image'));
|
||||
$edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image->uri)];
|
||||
$this->drupalGet('file-test/upload_required');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('You WIN!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests filename sanitization.
|
||||
*/
|
||||
public function testSanitization(): void {
|
||||
$file = $this->generateFile('TÉXT-œ', 64, 5, 'text');
|
||||
|
||||
$this->drupalGet('file-test/upload');
|
||||
// Upload a file with a name with uppercase and unicode characters.
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($file),
|
||||
'extensions' => 'txt',
|
||||
'is_image_file' => FALSE,
|
||||
];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Test that the file name has not been sanitized.
|
||||
$this->assertSession()->responseContains('File name is TÉXT-œ.txt.');
|
||||
|
||||
// Enable sanitization via the UI.
|
||||
$admin = $this->createUser(['administer site configuration']);
|
||||
$this->drupalLogin($admin);
|
||||
|
||||
// For now, just transliterate, with no other transformations.
|
||||
$options = [
|
||||
'filename_sanitization[transliterate]' => TRUE,
|
||||
'filename_sanitization[replace_whitespace]' => FALSE,
|
||||
'filename_sanitization[replace_non_alphanumeric]' => FALSE,
|
||||
'filename_sanitization[deduplicate_separators]' => FALSE,
|
||||
'filename_sanitization[lowercase]' => FALSE,
|
||||
'filename_sanitization[replacement_character]' => '-',
|
||||
];
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->submitForm($options, 'Save configuration');
|
||||
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Upload a file with a name with uppercase and unicode characters.
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Test that the file name has been transliterated.
|
||||
$this->assertSession()->responseContains('File name is TEXT-oe.txt.');
|
||||
// Make sure we got a message about the rename.
|
||||
$message = 'Your upload has been renamed to <em class="placeholder">TEXT-oe.txt</em>';
|
||||
$this->assertSession()->responseContains($message);
|
||||
|
||||
// Generate another file with a name with All The Things(tm) we care about.
|
||||
$file = $this->generateFile('S Pácê--táb# #--🙈', 64, 5, 'text');
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($file),
|
||||
'extensions' => 'txt',
|
||||
'is_image_file' => FALSE,
|
||||
];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Test that the file name has only been transliterated.
|
||||
$this->assertSession()->responseContains('File name is S Pace--tab# #---.txt.');
|
||||
|
||||
// Leave transliteration on and enable whitespace replacement.
|
||||
$this->drupalLogin($admin);
|
||||
$options['filename_sanitization[replace_whitespace]'] = TRUE;
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->submitForm($options, 'Save configuration');
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Try again with the monster filename.
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Test that the file name has been transliterated and whitespace replaced.
|
||||
$this->assertSession()->responseContains('File name is S--Pace--tab#-#---.txt.');
|
||||
|
||||
// Leave transliteration and whitespace replacement on, replace non-alpha.
|
||||
$this->drupalLogin($admin);
|
||||
$options['filename_sanitization[replace_non_alphanumeric]'] = TRUE;
|
||||
$options['filename_sanitization[replacement_character]'] = '_';
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->submitForm($options, 'Save configuration');
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Try again with the monster filename.
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Test that the file name has been transliterated, whitespace replaced with
|
||||
// '_', and non-alphanumeric characters replaced with '_'.
|
||||
$this->assertSession()->responseContains('File name is S__Pace--tab___--_.txt.');
|
||||
|
||||
// Now turn on the setting to remove duplicate separators.
|
||||
$this->drupalLogin($admin);
|
||||
$options['filename_sanitization[deduplicate_separators]'] = TRUE;
|
||||
$options['filename_sanitization[replacement_character]'] = '-';
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->submitForm($options, 'Save configuration');
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Try again with the monster filename.
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Test that the file name has been transliterated, whitespace replaced,
|
||||
// non-alphanumeric characters replaced, and duplicate separators removed.
|
||||
$this->assertSession()->responseContains('File name is S-Pace-tab.txt.');
|
||||
|
||||
// Finally, check the lowercase setting.
|
||||
$this->drupalLogin($admin);
|
||||
$options['filename_sanitization[lowercase]'] = TRUE;
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->submitForm($options, 'Save configuration');
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Generate another file since we're going to start getting collisions with
|
||||
// previously uploaded and renamed copies.
|
||||
$file = $this->generateFile('S Pácê--táb# #--🙈-2', 64, 5, 'text');
|
||||
$edit = [
|
||||
'files[file_test_upload]' => \Drupal::service('file_system')->realpath($file),
|
||||
'extensions' => 'txt',
|
||||
'is_image_file' => FALSE,
|
||||
];
|
||||
$this->drupalGet('file-test/upload');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Make sure all the sanitization options work as intended.
|
||||
$this->assertSession()->responseContains('File name is s-pace-tab-2.txt.');
|
||||
// Make sure we got a message about the rename.
|
||||
$message = 'Your upload has been renamed to <em class="placeholder">s-pace-tab-2.txt</em>';
|
||||
$this->assertSession()->responseContains($message);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests ajax upload to managed files.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class AjaxFileManagedMultipleTest extends WebDriverTestBase {
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file_test', 'file', 'file_module_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests if managed file form element works well with multiple files upload.
|
||||
*/
|
||||
public function testMultipleFilesUpload(): void {
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$file_storage = \Drupal::entityTypeManager()->getStorage('file');
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalGet(Url::fromRoute('file_module_test.managed_test', ['multiple' => TRUE]));
|
||||
|
||||
$paths = [];
|
||||
foreach (array_slice($this->drupalGetTestFiles('image'), 0, 2) as $image) {
|
||||
$paths[] = $image->filename;
|
||||
$page->attachFileToField('files[nested_file][]', $file_system->realpath($image->uri));
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
}
|
||||
|
||||
// Save entire form.
|
||||
$page->pressButton('Save');
|
||||
|
||||
$this->assertSession()->pageTextContains('The file ids are 1,2.');
|
||||
$this->assertCount(2, $file_storage->loadByProperties(['filename' => $paths]));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests file field validation functions.
|
||||
*
|
||||
* Values validated include the file type, max file size, max size per node,
|
||||
* and whether the field is required.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldValidateTest extends WebDriverTestBase {
|
||||
|
||||
use FileFieldCreationTrait;
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the validation message is displayed only once for ajax uploads.
|
||||
*/
|
||||
public function testAjaxValidationMessage(): void {
|
||||
$field_name = $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.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests the widget visibility settings for the Claro theme.
|
||||
*
|
||||
* The widget is intentionally tested with Claro as the default theme to test
|
||||
* the changes added in _claro_preprocess_file_and_image_widget().
|
||||
*
|
||||
* @see _claro_preprocess_file_and_image_widget()
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldWidgetClaroThemeTest extends FileFieldWidgetTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'claro';
|
||||
|
||||
/**
|
||||
* Tests that the field widget visibility settings are respected on the form.
|
||||
*/
|
||||
public function testWidgetDefaultVisibilitySettings(): void {
|
||||
// Set up an article node with all field storage settings set to TRUE.
|
||||
$type_name = 'article';
|
||||
$field_name = 'test_file_field_1';
|
||||
$field_storage_settings = [
|
||||
'display_field' => TRUE,
|
||||
'display_default' => TRUE,
|
||||
];
|
||||
$field_settings = [];
|
||||
$widget_settings = [];
|
||||
$field_storage = $this->createFileField($field_name, 'node', $type_name, $field_storage_settings, $field_settings, $widget_settings);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$test_file = current($this->getTestFiles('text'));
|
||||
$test_file_path = \Drupal::service('file_system')->realpath($test_file->uri);
|
||||
|
||||
// Fill out the form accordingly.
|
||||
$this->drupalGet("node/add/$type_name");
|
||||
$title = 'Fake Article Name 01';
|
||||
$page->findField('title[0][value]')->setValue($title);
|
||||
$page->attachFileToField('files[' . $field_name . '_0]', $test_file_path);
|
||||
$remove_button = $assert_session->waitForElementVisible('css', '[name="' . $field_name . '_0_remove_button"]');
|
||||
$this->assertNotNull($remove_button);
|
||||
$type = $assert_session->fieldExists("{$field_name}[0][display]")->getAttribute('type');
|
||||
$this->assertEquals($type, 'checkbox');
|
||||
$assert_session->checkboxChecked("{$field_name}[0][display]");
|
||||
|
||||
// Now, submit the same form and ensure that value is retained.
|
||||
$this->submitForm([], 'Save');
|
||||
$node = $this->drupalGetNodeByTitle($title, TRUE);
|
||||
$this->assertEquals(1, $node->get($field_name)->getValue()[0]['display'], 'The checkbox is enabled.');
|
||||
$this->drupalGet(Url::fromRoute('entity.node.edit_form', ['node' => $node->id()]));
|
||||
$assert_session->checkboxChecked("{$field_name}[0][display]");
|
||||
|
||||
// Submit the form again with the disabled value of the checkbox.
|
||||
$this->submitForm([
|
||||
"{$field_name}[0][display]" => FALSE,
|
||||
], 'Save');
|
||||
$node = $this->drupalGetNodeByTitle($title, TRUE);
|
||||
$this->assertEquals(0, $node->get($field_name)->getValue()[0]['display'], 'The checkbox is disabled.');
|
||||
$this->drupalGet(Url::fromRoute('entity.node.edit_form', ['node' => $node->id()]));
|
||||
$assert_session->checkboxNotChecked("{$field_name}[0][display]");
|
||||
|
||||
// Disable the field settings and ensure that the form is updated.
|
||||
$field_storage->setSetting('display_default', FALSE);
|
||||
$field_storage->save();
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet("node/add/$type_name");
|
||||
$title = 'Fake Article Name 02';
|
||||
$page->findField('title[0][value]')->setValue($title);
|
||||
$page->attachFileToField('files[' . $field_name . '_0]', $test_file_path);
|
||||
$remove_button = $assert_session->waitForElementVisible('css', '[name="' . $field_name . '_0_remove_button"]');
|
||||
$this->assertNotNull($remove_button);
|
||||
$type = $assert_session->fieldExists("{$field_name}[0][display]")->getAttribute('type');
|
||||
$this->assertEquals($type, 'checkbox');
|
||||
$assert_session->checkboxNotChecked("{$field_name}[0][display]");
|
||||
|
||||
// Submit the same form and ensure that value is retained.
|
||||
$this->submitForm([], 'Save');
|
||||
$node = $this->drupalGetNodeByTitle($title, TRUE);
|
||||
$this->assertEquals(0, $node->get($field_name)->getValue()[0]['display'], 'The checkbox is disabled.');
|
||||
$this->drupalGet(Url::fromRoute('entity.node.edit_form', ['node' => $node->id()]));
|
||||
$assert_session->checkboxNotChecked("{$field_name}[0][display]");
|
||||
|
||||
// Check the checkbox and ensure that it is submitted properly.
|
||||
$this->submitForm([
|
||||
"{$field_name}[0][display]" => TRUE,
|
||||
], 'Save');
|
||||
$node = $this->drupalGetNodeByTitle($title, TRUE);
|
||||
$this->assertEquals(1, $node->get($field_name)->getValue()[0]['display'], 'The checkbox is disabled because display_default option is marked as false.');
|
||||
$this->drupalGet(Url::fromRoute('entity.node.edit_form', ['node' => $node->id()]));
|
||||
$assert_session->checkboxChecked("{$field_name}[0][display]");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\FunctionalJavascript;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the file field widget, single and multi-valued, using AJAX upload.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileFieldWidgetTest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
use FileFieldCreationTrait;
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* An user with administration permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'file', 'file_module_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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 upload and remove buttons for multiple multi-valued File fields.
|
||||
*/
|
||||
public function testMultiValuedWidget(): void {
|
||||
$type_name = 'article';
|
||||
$field_name = 'test_file_field_1';
|
||||
$field_name2 = 'test_file_field_2';
|
||||
$cardinality = 3;
|
||||
$this->createFileField($field_name, 'node', $type_name, ['cardinality' => $cardinality]);
|
||||
$this->createFileField($field_name2, 'node', $type_name, ['cardinality' => $cardinality]);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$test_file = current($this->getTestFiles('text'));
|
||||
$test_file_path = \Drupal::service('file_system')
|
||||
->realpath($test_file->uri);
|
||||
|
||||
$this->drupalGet("node/add/$type_name");
|
||||
foreach ([$field_name2, $field_name] as $each_field_name) {
|
||||
for ($delta = 0; $delta < 3; $delta++) {
|
||||
$page->attachFileToField('files[' . $each_field_name . '_' . $delta . '][]', $test_file_path);
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', '[name="' . $each_field_name . '_' . $delta . '_remove_button"]'));
|
||||
$this->assertNull($assert_session->waitForButton($each_field_name . '_' . $delta . '_upload_button'));
|
||||
}
|
||||
}
|
||||
|
||||
$num_expected_remove_buttons = 6;
|
||||
|
||||
foreach ([$field_name, $field_name2] as $current_field_name) {
|
||||
// How many uploaded files for the current field are remaining.
|
||||
$remaining = 3;
|
||||
// Test clicking each "Remove" button. For extra robustness, test them out
|
||||
// of sequential order. They are 0-indexed, and get renumbered after each
|
||||
// iteration, so [1, 1, 0] means:
|
||||
// - First remove the 2nd file.
|
||||
// - Then remove what is then the 2nd file (was originally the 3rd file).
|
||||
// - Then remove the first file.
|
||||
foreach ([1, 1, 0] as $delta) {
|
||||
// Ensure we have the expected number of Remove buttons, and that they
|
||||
// are numbered sequentially.
|
||||
$buttons = $this->xpath('//input[@type="submit" and @value="Remove"]');
|
||||
$this->assertCount($num_expected_remove_buttons, $buttons, 'There are ' . $num_expected_remove_buttons . ' "Remove" buttons displayed.');
|
||||
foreach ($buttons as $i => $button) {
|
||||
$key = $i >= $remaining ? $i - $remaining : $i;
|
||||
$check_field_name = $field_name2;
|
||||
if ($current_field_name == $field_name && $i < $remaining) {
|
||||
$check_field_name = $field_name;
|
||||
}
|
||||
|
||||
$this->assertSame($check_field_name . '_' . $key . '_remove_button', $button->getAttribute('name'));
|
||||
}
|
||||
|
||||
$button_name = $current_field_name . '_' . $delta . '_remove_button';
|
||||
$remove_button = $assert_session->waitForButton($button_name);
|
||||
$remove_button->click();
|
||||
|
||||
$num_expected_remove_buttons--;
|
||||
$remaining--;
|
||||
|
||||
// Ensure an "Upload" button for the current field is displayed with the
|
||||
// correct name.
|
||||
$upload_button_name = $current_field_name . '_' . $remaining . '_upload_button';
|
||||
$this->assertNotNull($assert_session->waitForButton($upload_button_name));
|
||||
$button = $this->assertSession()->buttonExists($upload_button_name);
|
||||
$this->assertSame('Upload', $button->getValue());
|
||||
|
||||
// Verify that after removing a file, only one "Upload" button for each
|
||||
// possible field is displayed.
|
||||
$expected = $current_field_name == $field_name ? 1 : 2;
|
||||
$this->assertSession()->elementsCount('xpath', '//input[@type="submit" and @value="Upload"]', $expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uploading and remove buttons for a single-valued File field.
|
||||
*/
|
||||
public function testSingleValuedWidget(): void {
|
||||
$type_name = 'article';
|
||||
$field_name = 'test_file_field_1';
|
||||
$cardinality = 1;
|
||||
$this->createFileField($field_name, 'node', $type_name, ['cardinality' => $cardinality]);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$test_file = current($this->getTestFiles('text'));
|
||||
$test_file_path = \Drupal::service('file_system')
|
||||
->realpath($test_file->uri);
|
||||
|
||||
$this->drupalGet("node/add/$type_name");
|
||||
|
||||
$page->findField('title[0][value]')->setValue($this->randomString());
|
||||
|
||||
$page->attachFileToField('files[' . $field_name . '_0]', $test_file_path);
|
||||
$remove_button = $assert_session->waitForElementVisible('css', '[name="' . $field_name . '_0_remove_button"]');
|
||||
$this->assertNotNull($remove_button);
|
||||
$remove_button->click();
|
||||
$upload_field = $assert_session->waitForElementVisible('css', 'input[type="file"]');
|
||||
$this->assertNotEmpty($upload_field);
|
||||
$page->attachFileToField('files[' . $field_name . '_0]', $test_file_path);
|
||||
$remove_button = $assert_session->waitForElementVisible('css', '[name="' . $field_name . '_0_remove_button"]');
|
||||
$this->assertNotNull($remove_button);
|
||||
$page->pressButton('Save');
|
||||
$page->hasContent($test_file->name);
|
||||
|
||||
// Create a new node and try to upload a file with an invalid extension.
|
||||
$test_image = current($this->getTestFiles('image'));
|
||||
$test_image_path = \Drupal::service('file_system')
|
||||
->realpath($test_image->uri);
|
||||
|
||||
$this->drupalGet("node/add/$type_name");
|
||||
|
||||
$page->findField('title[0][value]')->setValue($this->randomString());
|
||||
$page->attachFileToField('files[' . $field_name . '_0]', $test_image_path);
|
||||
$messages = $assert_session->waitForElementVisible('css', '.file-upload-js-error');
|
||||
$this->assertEquals('The selected file image-test.png cannot be uploaded. Only files with the following extensions are allowed: txt.', $messages->getText());
|
||||
// Make sure the error disappears when a valid file is uploaded.
|
||||
$page->attachFileToField('files[' . $field_name . '_0]', $test_file_path);
|
||||
$remove_button = $assert_session->waitForElementVisible('css', '[name="' . $field_name . '_0_remove_button"]');
|
||||
$this->assertNotEmpty($remove_button);
|
||||
$this->assertEmpty($this->cssSelect('.file-upload-js-error'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uploading more files than allowed at once.
|
||||
*/
|
||||
public function testUploadingMoreFilesThanAllowed(): void {
|
||||
$type_name = 'article';
|
||||
$field_name = 'test_file_field_1';
|
||||
$cardinality = 2;
|
||||
$this->createFileField($field_name, 'node', $type_name, ['cardinality' => $cardinality]);
|
||||
|
||||
$web_driver = $this->getSession()->getDriver();
|
||||
$file_system = \Drupal::service('file_system');
|
||||
|
||||
$files = array_slice($this->getTestFiles('text'), 0, 3);
|
||||
$real_paths = [];
|
||||
foreach ($files as $file) {
|
||||
$real_paths[] = $file_system->realpath($file->uri);
|
||||
}
|
||||
$remote_paths = [];
|
||||
foreach ($real_paths as $path) {
|
||||
$remote_paths[] = $web_driver->uploadFileAndGetRemoteFilePath($path);
|
||||
}
|
||||
|
||||
// Tests that uploading multiple remote files works with remote path.
|
||||
$this->drupalGet("node/add/$type_name");
|
||||
$multiple_field = $this->getSession()->getPage()->findField('files[test_file_field_1_0][]');
|
||||
$multiple_field->setValue(implode("\n", $remote_paths));
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->pageTextContains("Field {$field_name} can only hold {$cardinality} values but there were 3 uploaded. The following files have been omitted as a result: text-2.txt.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a sample file of the specified type.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface
|
||||
* The new unsaved file entity.
|
||||
*/
|
||||
public function getTestFile($type_name, $size = NULL) {
|
||||
// Get a file to upload.
|
||||
$file = current($this->getTestFiles($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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\FunctionalJavascript;
|
||||
|
||||
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'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with administration permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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(): void {
|
||||
// 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->submitForm([], 'Save');
|
||||
|
||||
// Remove, then Submit.
|
||||
$remove_button_title = $multiple ? 'Remove selected' : '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->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('The file ids are .');
|
||||
|
||||
// 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->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('The file ids are .');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fid of the last inserted file.
|
||||
*/
|
||||
protected function getLastFileId() {
|
||||
return (int) \Drupal::entityQueryAggregate('file')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('fid', 'max')
|
||||
->execute()[0]['fid_max'];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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}
|
||||
*/
|
||||
protected static $modules = ['node', 'file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @todo Remove this when issue #2905597 is fixed.
|
||||
* @see https://www.drupal.org/node/2905597
|
||||
*/
|
||||
protected $originalDisplayErrorsValue;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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' => 'bin 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(): void {
|
||||
// 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(): void {
|
||||
$session = $this->getSession();
|
||||
|
||||
// Create a test file that exceeds the maximum POST size with 1 kilobyte.
|
||||
$post_max_size = (int) Bytes::toNumber(ini_get('post_max_size'));
|
||||
$invalid_file = 'public://exceeding_post_max_size.bin';
|
||||
$file = fopen($invalid_file, 'wb');
|
||||
fseek($file, $post_max_size + 1024);
|
||||
fwrite($file, '0');
|
||||
fclose($file);
|
||||
|
||||
// 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. 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()->statusMessageContainsAfterWait('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size', 'error');
|
||||
|
||||
// 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()->statusMessageNotExistsAfterWait('error');
|
||||
}
|
||||
|
||||
}
|
||||
190
web/core/modules/file/tests/src/Kernel/AccessTest.php
Normal file
190
web/core/modules/file/tests/src/Kernel/AccessTest.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests for the File access control.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class AccessTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file', 'system', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('file');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('file', ['file_usage']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 'update' and 'delete' access to file entities.
|
||||
*/
|
||||
public function testFileAccess(): void {
|
||||
// Create a user so the tested users do not have the magic ID of user 1.
|
||||
$this->createUser();
|
||||
|
||||
$user_any = $this->createUser([
|
||||
'delete any file',
|
||||
]);
|
||||
$this->assertGreaterThan(1, (int) $user_any->id());
|
||||
|
||||
$user_own = $this->createUser([
|
||||
'delete own files',
|
||||
]);
|
||||
|
||||
$test_files = $this->getTestFiles('text');
|
||||
$file1 = File::create((array) $test_files[0]);
|
||||
$file1->set('uid', $user_any->id());
|
||||
$file1->save();
|
||||
$file2 = File::create((array) $test_files[1]);
|
||||
$file2->set('uid', $user_own->id());
|
||||
$file2->save();
|
||||
|
||||
// User with "* any file" permissions should delete all files and update
|
||||
// their own.
|
||||
$this->assertTrue($file1->access('delete', $user_any));
|
||||
$this->assertTrue($file1->access('update', $user_any));
|
||||
$this->assertTrue($file2->access('delete', $user_any));
|
||||
$this->assertFalse($file2->access('update', $user_any));
|
||||
|
||||
// User with "* own files" permissions should access only own files.
|
||||
$this->assertFalse($file1->access('delete', $user_own));
|
||||
$this->assertFalse($file1->access('update', $user_own));
|
||||
$this->assertTrue($file2->access('delete', $user_own));
|
||||
$this->assertTrue($file2->access('update', $user_own));
|
||||
|
||||
// Ensure cacheability metadata is correct.
|
||||
/** @var \Drupal\Core\Access\AccessResult $access */
|
||||
$access = $file2->access('delete', $user_any, TRUE);
|
||||
$this->assertSame(['user.permissions'], $access->getCacheContexts());
|
||||
$this->assertSame([], $access->getCacheTags());
|
||||
/** @var \Drupal\Core\Access\AccessResult $access */
|
||||
$access = $file2->access('delete', $user_own, TRUE);
|
||||
$this->assertSame(['user.permissions', 'user'], $access->getCacheContexts());
|
||||
$this->assertSame(['file:2'], $access->getCacheTags());
|
||||
/** @var \Drupal\Core\Access\AccessResult $access */
|
||||
$access = $file2->access('update', $user_any, TRUE);
|
||||
$this->assertSame([], $access->getCacheContexts());
|
||||
$this->assertSame([], $access->getCacheTags());
|
||||
/** @var \Drupal\Core\Access\AccessResult $access */
|
||||
$access = $file2->access('update', $user_own, TRUE);
|
||||
$this->assertSame([], $access->getCacheContexts());
|
||||
$this->assertSame([], $access->getCacheTags());
|
||||
|
||||
// User without permissions should not be able to delete files even if they
|
||||
// are the owner.
|
||||
$user_none = $this->createUser();
|
||||
$file3 = File::create([
|
||||
'uid' => $user_none->id(),
|
||||
'filename' => 'druplicon.txt',
|
||||
'filemime' => 'text/plain',
|
||||
]);
|
||||
$this->assertFalse($file3->access('delete', $user_none));
|
||||
$this->assertTrue($file3->access('update', $user_none));
|
||||
|
||||
// Create a file with no user entity.
|
||||
$file4 = File::create([
|
||||
'filename' => 'druplicon.txt',
|
||||
'filemime' => 'text/plain',
|
||||
]);
|
||||
$this->assertFalse($file4->access('delete', $user_own));
|
||||
$this->assertFalse($file4->access('update', $user_own));
|
||||
$this->assertTrue($file4->access('delete', $user_any));
|
||||
$this->assertFalse($file4->access('update', $user_any));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file entity field access.
|
||||
*
|
||||
* @see \Drupal\file\FileAccessControlHandler::checkFieldAccess()
|
||||
*/
|
||||
public function testCheckFieldAccess(): void {
|
||||
$this->setUpCurrentUser();
|
||||
/** @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 is always denied even for user 1.
|
||||
*
|
||||
* @see \Drupal\file\FileAccessControlHandler::checkCreateAccess()
|
||||
*/
|
||||
public function testCreateAccess(): void {
|
||||
$user1 = $this->createUser([
|
||||
'delete own files',
|
||||
]);
|
||||
|
||||
$this->assertSame('1', $user1->id());
|
||||
|
||||
$file = File::create([
|
||||
'uid' => $user1->id(),
|
||||
'filename' => 'druplicon.txt',
|
||||
'filemime' => 'text/plain',
|
||||
]);
|
||||
$this->assertFalse($file->access('create'));
|
||||
|
||||
\Drupal::currentUser()->setAccount($user1);
|
||||
$this->assertFalse($file->access('create'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests cacheability metadata.
|
||||
*/
|
||||
public function testFileCacheability(): void {
|
||||
$file = File::create([
|
||||
'filename' => 'green-scarf',
|
||||
'uri' => 'private://green-scarf',
|
||||
'filemime' => 'text/plain',
|
||||
]);
|
||||
$file->setPermanent();
|
||||
$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->createUser();
|
||||
$file->setOwnerId($account->id())->save();
|
||||
$this->assertSame(['user'], $file->access('view', $account, TRUE)->getCacheContexts());
|
||||
$this->assertSame(['user'], $file->access('download', $account, TRUE)->getCacheContexts());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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());
|
||||
}
|
||||
|
||||
}
|
||||
220
web/core/modules/file/tests/src/Kernel/CopyTest.php
Normal file
220
web/core/modules/file/tests/src/Kernel/CopyTest.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Entity\EntityTypeManager;
|
||||
use Drupal\Core\File\Exception\FileExistsException;
|
||||
use Drupal\Core\File\Exception\InvalidStreamWrapperException;
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileRepository;
|
||||
|
||||
/**
|
||||
* Tests the file copy function.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\file\FileRepository
|
||||
* @group file
|
||||
*/
|
||||
class CopyTest extends FileManagedUnitTestBase {
|
||||
|
||||
/**
|
||||
* The file repository service under test.
|
||||
*
|
||||
* @var \Drupal\file\FileRepository
|
||||
*/
|
||||
protected $fileRepository;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->fileRepository = $this->container->get('file.repository');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file copying in the normal, base case.
|
||||
*
|
||||
* @covers ::copy
|
||||
*/
|
||||
public function testNormal(): void {
|
||||
$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 = $this->fileRepository->copy(clone $source, $desired_uri, FileExists::Error);
|
||||
|
||||
// Check the return status and that the contents changed.
|
||||
$this->assertNotFalse($result, 'File copied successfully.');
|
||||
$this->assertEquals($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->assertEquals($result->getFileUri(), $desired_uri, 'The copied file entity has the desired filepath.');
|
||||
$this->assertFileExists($source->getFileUri());
|
||||
$this->assertFileExists($result->getFileUri());
|
||||
|
||||
// Reload the file from the database and check that the changes were
|
||||
// actually saved.
|
||||
$this->assertFileUnchanged($result, File::load($result->id()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests renaming when copying over a file that already exists.
|
||||
*
|
||||
* @covers ::copy
|
||||
*/
|
||||
public function testExistingRename(): void {
|
||||
// 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 = $this->fileRepository->copy(clone $source, $target->getFileUri(), FileExists::Rename);
|
||||
|
||||
// Check the return status and that the contents changed.
|
||||
$this->assertNotFalse($result, 'File copied successfully.');
|
||||
$this->assertEquals($contents, file_get_contents($result->getFileUri()), 'Contents of file were copied correctly.');
|
||||
$this->assertNotEquals($source->getFileUri(), $result->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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests replacement when copying over a file that already exists.
|
||||
*
|
||||
* @covers ::copy
|
||||
*/
|
||||
public function testExistingReplace(): void {
|
||||
// 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 = $this->fileRepository->copy(clone $source, $target->getFileUri(), FileExists::Replace);
|
||||
|
||||
// Check the return status and that the contents changed.
|
||||
$this->assertNotFalse($result, 'File copied successfully.');
|
||||
$this->assertEquals($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that copying over an existing file fails when instructed to do so.
|
||||
*
|
||||
* @covers ::copy
|
||||
*/
|
||||
public function testExistingError(): void {
|
||||
$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.
|
||||
try {
|
||||
$this->fileRepository->copy(clone $source, $target->getFileUri(), FileExists::Error);
|
||||
$this->fail('expected FileExistsException');
|
||||
}
|
||||
// FileExistsException is a subclass of FileException.
|
||||
catch (FileExistsException $e) {
|
||||
$this->assertStringContainsString("could not be copied because a file by that name already exists in the destination directory", $e->getMessage());
|
||||
}
|
||||
// Check the contents were not changed.
|
||||
$this->assertEquals($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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for an invalid stream wrapper.
|
||||
*
|
||||
* @covers ::copy
|
||||
*/
|
||||
public function testInvalidStreamWrapper(): void {
|
||||
$this->expectException(InvalidStreamWrapperException::class);
|
||||
$this->expectExceptionMessage('Invalid stream wrapper: foo://');
|
||||
$source = $this->createFile();
|
||||
$this->fileRepository->copy($source, 'foo://');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for entity storage exception.
|
||||
*
|
||||
* @covers ::copy
|
||||
*/
|
||||
public function testEntityStorageException(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManager $entityTypeManager */
|
||||
$entityTypeManager = $this->prophesize(EntityTypeManager::class);
|
||||
$entityTypeManager->getStorage('file')
|
||||
->willThrow(EntityStorageException::class);
|
||||
|
||||
$fileRepository = new FileRepository(
|
||||
$this->container->get('file_system'),
|
||||
$this->container->get('stream_wrapper_manager'),
|
||||
$entityTypeManager->reveal(),
|
||||
$this->container->get('module_handler'),
|
||||
$this->container->get('file.usage'),
|
||||
$this->container->get('current_user')
|
||||
);
|
||||
|
||||
$this->expectException(EntityStorageException::class);
|
||||
$source = $this->createFile();
|
||||
$target = $this->createFile();
|
||||
$fileRepository->copy($source, $target->getFileUri(), FileExists::Replace);
|
||||
}
|
||||
|
||||
}
|
||||
107
web/core/modules/file/tests/src/Kernel/DeleteTest.php
Normal file
107
web/core/modules/file/tests/src/Kernel/DeleteTest.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
|
||||
/**
|
||||
* 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(): void {
|
||||
$file = $this->createFile();
|
||||
|
||||
// Check that deletion removes the file and database record.
|
||||
$this->assertFileExists($file->getFileUri());
|
||||
$file->delete();
|
||||
$this->assertFileHooksCalled(['delete']);
|
||||
$this->assertFileDoesNotExist($file->getFileUri());
|
||||
$this->assertNull(File::load($file->id()), 'File was removed from the database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries deleting a file that is in use.
|
||||
*/
|
||||
public function testInUse(): void {
|
||||
// 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->assertEquals([1 => 1], $usage['testing']['test'], 'Test file is still in use.');
|
||||
$this->assertFileExists($file->getFileUri());
|
||||
$this->assertNotEmpty(File::load($file->id()), 'File still exists in the database.');
|
||||
|
||||
// Clear out the call to hook_file_load().
|
||||
FileTestHelper::reset();
|
||||
|
||||
$file_usage->delete($file, 'testing', 'test', 1);
|
||||
$usage = $file_usage->listUsage($file);
|
||||
$this->assertFileHooksCalled(['load', 'update']);
|
||||
$this->assertEmpty($usage, 'File usage data was removed.');
|
||||
$this->assertFileExists($file->getFileUri());
|
||||
$file = File::load($file->id());
|
||||
$this->assertNotEmpty($file, 'File still exists in the database.');
|
||||
$this->assertTrue($file->isTemporary(), 'File is temporary.');
|
||||
FileTestHelper::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. We use an UPDATE statement because using the API
|
||||
// would set the timestamp.
|
||||
Database::getConnection()->update('file_managed')
|
||||
->fields([
|
||||
'changed' => \Drupal::time()->getRequestTime() - ($this->config('system.file')->get('temporary_maximum_age') + 3),
|
||||
])
|
||||
->condition('fid', $file->id())
|
||||
->execute();
|
||||
\Drupal::service('cron')->run();
|
||||
|
||||
// file_cron() loads
|
||||
$this->assertFileHooksCalled(['delete']);
|
||||
$this->assertFileDoesNotExist($file->getFileUri());
|
||||
$this->assertNull(File::load($file->id()), 'File was removed from the database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to run cron deletion on file deleted from the file-system.
|
||||
*/
|
||||
public function testCronDeleteNonExistingTemporary(): void {
|
||||
$file = $this->createFile();
|
||||
// Delete the file, but leave it in the file_managed table.
|
||||
\Drupal::service('file_system')->delete($file->getFileUri());
|
||||
$this->assertFileDoesNotExist($file->getFileUri());
|
||||
$this->assertInstanceOf(File::class, File::load($file->id()));
|
||||
|
||||
// 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.
|
||||
\Drupal::database()->update('file_managed')
|
||||
->fields([
|
||||
'changed' => \Drupal::time()->getRequestTime() - ($this->config('system.file')->get('temporary_maximum_age') + 3),
|
||||
])
|
||||
->condition('fid', $file->id())
|
||||
->execute();
|
||||
\Drupal::service('cron')->run();
|
||||
|
||||
$this->assertNull(File::load($file->id()), 'File was removed from the database.');
|
||||
}
|
||||
|
||||
}
|
||||
204
web/core/modules/file/tests/src/Kernel/FileItemTest.php
Normal file
204
web/core/modules/file/tests/src/Kernel/FileItemTest.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Field\FieldItemInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
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\file\Plugin\Field\FieldType\FileItem;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests using entity fields of the file field type.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileItemTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file'];
|
||||
|
||||
/**
|
||||
* Created file entity.
|
||||
*
|
||||
* @var \Drupal\file\Entity\File
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* Directory where the sample files are stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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(): void {
|
||||
// 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->assertEquals('default:file', $handler_id);
|
||||
|
||||
// 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->assertInstanceOf(FieldItemListInterface::class, $entity->file_test);
|
||||
$this->assertInstanceOf(FieldItemInterface::class, $entity->file_test[0]);
|
||||
$this->assertEquals($this->file->id(), $entity->file_test->target_id);
|
||||
$this->assertEquals(1, $entity->file_test->display);
|
||||
$this->assertEquals($description, $entity->file_test->description);
|
||||
$this->assertEquals($this->file->getFileUri(), $entity->file_test->entity->getFileUri());
|
||||
$this->assertEquals($this->file->id(), $entity->file_test->entity->id());
|
||||
$this->assertEquals($this->file->uuid(), $entity->file_test->entity->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->assertEquals($entity->file_test->entity->id(), $file2->id());
|
||||
$this->assertEquals($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();
|
||||
|
||||
/** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
|
||||
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
|
||||
|
||||
$this->assertEquals($this->directory, dirname($stream_wrapper_manager::getTarget($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 = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay('entity_test', 'entity_test');
|
||||
$display->setComponent('file_test', [
|
||||
'label' => 'above',
|
||||
'type' => 'file_default',
|
||||
'weight' => 1,
|
||||
])->save();
|
||||
$entity = EntityTest::create();
|
||||
$entity->file_test = ['entity' => $file3];
|
||||
$uri = $file3->getFileUri();
|
||||
$output = \Drupal::entityTypeManager()
|
||||
->getViewBuilder('entity_test')
|
||||
->view($entity, 'default');
|
||||
\Drupal::service('renderer')->renderRoot($output);
|
||||
$this->assertTrue(!empty($entity->file_test->entity));
|
||||
$this->assertEquals($uri, $entity->file_test->entity->getFileUri());
|
||||
|
||||
// Test file URIs with empty and custom directories.
|
||||
$this->validateFileUriForDirectory(
|
||||
'', 'public://'
|
||||
);
|
||||
$this->validateFileUriForDirectory(
|
||||
'custom_directory/subdir', 'public://custom_directory/subdir/'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file URIs generated for a given file directory.
|
||||
*
|
||||
* @param string $file_directory
|
||||
* The file directory to test (e.g., empty or 'custom_directory/subdir').
|
||||
* @param string $expected_start
|
||||
* The expected starting string of the file URI (e.g., 'public://').
|
||||
*/
|
||||
private function validateFileUriForDirectory(string $file_directory, string $expected_start): void {
|
||||
// Mock the field definition with the specified file directory.
|
||||
$definition = $this->createMock(FieldDefinitionInterface::class);
|
||||
$definition->expects($this->any())
|
||||
->method('getSettings')
|
||||
->willReturn([
|
||||
'file_extensions' => 'txt',
|
||||
'file_directory' => $file_directory,
|
||||
'uri_scheme' => 'public',
|
||||
'display_default' => TRUE,
|
||||
]);
|
||||
|
||||
// Generate a sample file value.
|
||||
$value = FileItem::generateSampleValue($definition);
|
||||
$this->assertNotEmpty($value);
|
||||
|
||||
// Load the file entity and get its URI.
|
||||
$fid = $value['target_id'];
|
||||
$file = File::load($fid);
|
||||
$fileUri = $file->getFileUri();
|
||||
|
||||
// Verify the file URI starts with the expected protocol and structure.
|
||||
$this->assertStringStartsWith($expected_start, $fileUri);
|
||||
$this->assertMatchesRegularExpression('#^' . preg_quote($expected_start, '#') . '[^/]+#', $fileUri);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'file',
|
||||
'image',
|
||||
'entity_test',
|
||||
'field',
|
||||
'user',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* A user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('file');
|
||||
$this->installSchema('file', 'file_usage');
|
||||
|
||||
$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): void {
|
||||
$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(),
|
||||
],
|
||||
]);
|
||||
|
||||
// Enforce the file to be new as file size is checked only for new files.
|
||||
$entity_test->field_test_file->entity->enforceIsNew();
|
||||
$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());
|
||||
|
||||
// File size is not checked for already existing files.
|
||||
$entity_test->field_test_file->entity->enforceIsNew(FALSE);
|
||||
$result = $entity_test->validate();
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals('field_test_file.0', $result->get(0)->getPropertyPath());
|
||||
$this->assertEquals('Only files with the following extensions are allowed: <em class="placeholder">jpg|png</em>.', (string) $result->get(0)->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 static function getFileTypes() {
|
||||
return [['file'], ['image']];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests access to managed files.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileManagedAccessTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'file',
|
||||
'system',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests if public file is always accessible.
|
||||
*/
|
||||
public function testFileAccess(): void {
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('file');
|
||||
$this->installSchema('file', ['file_usage']);
|
||||
$this->installConfig('user');
|
||||
|
||||
$anonymous = User::create(['uid' => 0, 'name' => '']);
|
||||
$anonymous->save();
|
||||
user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, ['access content']);
|
||||
|
||||
// Create an authenticated user to check file access.
|
||||
$account = $this->createUser(['access site reports', 'access content'], NULL, FALSE, ['uid' => 2]);
|
||||
|
||||
// Create a new file entity in the public:// stream wrapper.
|
||||
$file_public = File::create([
|
||||
'uid' => 1,
|
||||
'filename' => 'drupal.txt',
|
||||
'uri' => 'public://drupal.txt',
|
||||
'status' => FileInterface::STATUS_PERMANENT,
|
||||
]);
|
||||
$file_public->save();
|
||||
|
||||
$this->assertTrue($file_public->access('view', $account));
|
||||
$this->assertTrue($file_public->access('download', $account));
|
||||
|
||||
$this->assertTrue($file_public->access('view', $anonymous));
|
||||
$this->assertTrue($file_public->access('download', $anonymous));
|
||||
|
||||
// Create a new file entity in the private:// stream wrapper.
|
||||
$file_private = File::create([
|
||||
'uid' => 1,
|
||||
'filename' => 'drupal.txt',
|
||||
'uri' => 'private://drupal.txt',
|
||||
'status' => FileInterface::STATUS_PERMANENT,
|
||||
]);
|
||||
$file_private->save();
|
||||
|
||||
$this->assertFalse($file_private->access('view', $account));
|
||||
$this->assertFalse($file_private->access('download', $account));
|
||||
|
||||
$this->assertFalse($file_private->access('view', $anonymous));
|
||||
$this->assertFalse($file_private->access('download', $anonymous));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Provides a base class for testing file uploads and hook invocations.
|
||||
*/
|
||||
abstract class FileManagedUnitTestBase extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file_test', 'file', 'system', 'field', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Clear out any hook calls.
|
||||
FileTestHelper::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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the specified file hooks were called only once.
|
||||
*
|
||||
* @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(FileTestHelper::getAllCalls()));
|
||||
|
||||
// Determine if there were any expected that were not called.
|
||||
$uncalled = array_diff($expected, $actual);
|
||||
if (count($uncalled)) {
|
||||
$this->assertTrue(FALSE, sprintf('Expected hooks %s to be called but %s was not called.', implode(', ', $expected), implode(', ', $uncalled)));
|
||||
}
|
||||
else {
|
||||
$this->assertTrue(TRUE, sprintf('All the expected hooks were called: %s', empty($expected) ? '(none)' : implode(', ', $expected)));
|
||||
}
|
||||
|
||||
// Determine if there were any unexpected calls.
|
||||
$unexpected = array_diff($actual, $expected);
|
||||
if (count($unexpected)) {
|
||||
$this->assertTrue(FALSE, sprintf('Unexpected hooks were called: %s.', 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(FileTestHelper::getCalls($hook));
|
||||
|
||||
if (!isset($message)) {
|
||||
if ($actual_count == $expected_count) {
|
||||
$message = "hook_file_$hook was called correctly.";
|
||||
}
|
||||
elseif ($expected_count == 0) {
|
||||
$message = "hook_file_$hook was not expected to be called but was actually called $actual_count time(s).";
|
||||
}
|
||||
else {
|
||||
$message = "hook_file_$hook was expected to be called $expected_count time(s) but was called $actual_count time(s).";
|
||||
}
|
||||
}
|
||||
$this->assertEquals($expected_count, $actual_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->assertEquals($before->id(), $after->id(), 'File id is the same');
|
||||
$this->assertEquals($before->getOwner()->id(), $after->getOwner()->id(), 'File owner is the same');
|
||||
$this->assertEquals($before->getFilename(), $after->getFilename(), 'File name is the same');
|
||||
$this->assertEquals($before->getFileUri(), $after->getFileUri(), 'File path is the same');
|
||||
$this->assertEquals($before->getMimeType(), $after->getMimeType(), 'File MIME type is the same');
|
||||
$this->assertEquals($before->getSize(), $after->getSize(), 'File size is the same');
|
||||
$this->assertEquals($before->isPermanent(), $after->isPermanent(), 'File status is the same');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->assertNotEquals($file1->id(), $file2->id(), 'Files have different ids');
|
||||
$this->assertNotEquals($file1->getFileUri(), $file2->getFileUri(), 'Files have different paths');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->assertEquals($file1->id(), $file2->id(), 'Files have the same ids');
|
||||
$this->assertEquals($file1->getFileUri(), $file2->getFileUri(), 'Files have the same path');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and saves a file, asserting that it was saved.
|
||||
*
|
||||
* @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.
|
||||
// Verify that the file was added to the database.
|
||||
$this->assertGreaterThan(0, $file->id());
|
||||
|
||||
\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.
|
||||
// cSpell:disable-next-line
|
||||
$filepath = 'Файл для тестирования ' . $this->randomMachineName();
|
||||
}
|
||||
if (!isset($scheme)) {
|
||||
$scheme = 'public';
|
||||
}
|
||||
$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->assertFileExists($filepath);
|
||||
return $filepath;
|
||||
}
|
||||
|
||||
}
|
||||
227
web/core/modules/file/tests/src/Kernel/FileRepositoryTest.php
Normal file
227
web/core/modules/file/tests/src/Kernel/FileRepositoryTest.php
Normal file
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Entity\EntityTypeManager;
|
||||
use Drupal\Core\File\Exception\FileExistsException;
|
||||
use Drupal\Core\File\Exception\InvalidStreamWrapperException;
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileRepository;
|
||||
|
||||
/**
|
||||
* Tests the FileRepository.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\file\FileRepository
|
||||
* @group file
|
||||
*/
|
||||
class FileRepositoryTest extends FileManagedUnitTestBase {
|
||||
|
||||
/**
|
||||
* The file repository service under test.
|
||||
*
|
||||
* @var \Drupal\file\FileRepository
|
||||
*/
|
||||
protected $fileRepository;
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->fileRepository = $this->container->get('file.repository');
|
||||
$this->fileSystem = $this->container->get('file_system');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the writeData() method.
|
||||
*
|
||||
* @covers ::writeData
|
||||
*/
|
||||
public function testWithFilename(): void {
|
||||
$contents = $this->randomMachineName();
|
||||
|
||||
// Using filename with non-latin characters.
|
||||
// cSpell:disable-next-line
|
||||
$filename = 'Текстовый файл.txt';
|
||||
|
||||
$result = $this->fileRepository->writeData($contents, 'public://' . $filename);
|
||||
$this->assertNotFalse($result, 'Unnamed file saved correctly.');
|
||||
|
||||
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
|
||||
assert($stream_wrapper_manager instanceof StreamWrapperManagerInterface);
|
||||
$this->assertEquals('public', $stream_wrapper_manager::getScheme($result->getFileUri()), "File was placed in Drupal's files directory.");
|
||||
$this->assertEquals($filename, \Drupal::service('file_system')->basename($result->getFileUri()), 'File was named correctly.');
|
||||
$this->assertEquals($contents, file_get_contents($result->getFileUri()), 'Contents of the file are correct.');
|
||||
$this->assertEquals('text/plain', $result->getMimeType(), '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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests writeData() when renaming around an existing file.
|
||||
*
|
||||
* @covers ::writeData
|
||||
*/
|
||||
public function testExistingRename(): void {
|
||||
// Setup a file to overwrite.
|
||||
$existing = $this->createFile();
|
||||
$contents = $this->randomMachineName();
|
||||
|
||||
$result = $this->fileRepository->writeData($contents, $existing->getFileUri());
|
||||
$this->assertNotFalse($result, 'File saved successfully.');
|
||||
|
||||
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
|
||||
assert($stream_wrapper_manager instanceof StreamWrapperManagerInterface);
|
||||
$this->assertEquals('public', $stream_wrapper_manager::getScheme($result->getFileUri()), "File was placed in Drupal's files directory.");
|
||||
$this->assertEquals($existing->getFilename(), $result->getFilename(), 'Filename was set to the basename of the source, rather than that of the renamed file.');
|
||||
$this->assertEquals($contents, file_get_contents($result->getFileUri()), 'Contents of the file are correct.');
|
||||
$this->assertEquals('application/octet-stream', $result->getMimeType(), '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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests writeData() when replacing an existing file.
|
||||
*
|
||||
* @covers ::writeData
|
||||
*/
|
||||
public function testExistingReplace(): void {
|
||||
// Setup a file to overwrite.
|
||||
$existing = $this->createFile();
|
||||
$contents = $this->randomMachineName();
|
||||
|
||||
$result = $this->fileRepository->writeData($contents, $existing->getFileUri(), FileExists::Replace);
|
||||
$this->assertNotFalse($result, 'File saved successfully.');
|
||||
|
||||
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
|
||||
assert($stream_wrapper_manager instanceof StreamWrapperManagerInterface);
|
||||
$this->assertEquals('public', $stream_wrapper_manager::getScheme($result->getFileUri()), "File was placed in Drupal's files directory.");
|
||||
$this->assertEquals($existing->getFilename(), $result->getFilename(), 'Filename was set to the basename of the existing file, rather than preserving the original name.');
|
||||
$this->assertEquals($contents, file_get_contents($result->getFileUri()), 'Contents of the file are correct.');
|
||||
$this->assertEquals('application/octet-stream', $result->getMimeType(), '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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that writeData() fails overwriting an existing file.
|
||||
*
|
||||
* @covers ::writeData
|
||||
*/
|
||||
public function testExistingError(): void {
|
||||
$contents = $this->randomMachineName();
|
||||
$existing = $this->createFile(NULL, $contents);
|
||||
|
||||
// Check the overwrite error.
|
||||
try {
|
||||
$this->fileRepository->writeData('asdf', $existing->getFileUri(), FileExists::Error);
|
||||
$this->fail('expected FileExistsException');
|
||||
}
|
||||
// FileExistsException is a subclass of FileException.
|
||||
catch (FileExistsException $e) {
|
||||
$this->assertStringContainsString("could not be copied because a file by that name already exists in the destination directory", $e->getMessage());
|
||||
}
|
||||
$this->assertEquals($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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for an invalid stream wrapper.
|
||||
*
|
||||
* @covers ::writeData
|
||||
*/
|
||||
public function testInvalidStreamWrapper(): void {
|
||||
$this->expectException(InvalidStreamWrapperException::class);
|
||||
$this->expectExceptionMessage('Invalid stream wrapper: foo://');
|
||||
$this->fileRepository->writeData('asdf', 'foo://');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for entity storage exception.
|
||||
*
|
||||
* @covers ::writeData
|
||||
*/
|
||||
public function testEntityStorageException(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManager $entityTypeManager */
|
||||
$entityTypeManager = $this->prophesize(EntityTypeManager::class);
|
||||
$entityTypeManager->getStorage('file')
|
||||
->willThrow(EntityStorageException::class);
|
||||
|
||||
$fileRepository = new FileRepository(
|
||||
$this->container->get('file_system'),
|
||||
$this->container->get('stream_wrapper_manager'),
|
||||
$entityTypeManager->reveal(),
|
||||
$this->container->get('module_handler'),
|
||||
$this->container->get('file.usage'),
|
||||
$this->container->get('current_user')
|
||||
);
|
||||
|
||||
$this->expectException(EntityStorageException::class);
|
||||
$target = $this->createFile();
|
||||
$fileRepository->writeData('asdf', $target->getFileUri(), FileExists::Replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests loading a file by URI.
|
||||
*
|
||||
* @covers ::loadByUri
|
||||
*/
|
||||
public function testLoadByUri(): void {
|
||||
$source = $this->createFile();
|
||||
$result = $this->fileRepository->loadByUri($source->getFileUri());
|
||||
$this->assertSameFile($source, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests loading a file by case-sensitive URI.
|
||||
*
|
||||
* @covers ::loadByUri
|
||||
*/
|
||||
public function testLoadByUriCaseSensitive(): void {
|
||||
$source = $this->createFile('FooBar.txt');
|
||||
$result = $this->fileRepository->loadByUri('public://FooBar.txt');
|
||||
$this->assertSameFile($source, $result);
|
||||
$result = $this->fileRepository->loadByUri('public://foobar.txt');
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
|
||||
/**
|
||||
* Tests file_save_upload().
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileSaveUploadTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'file',
|
||||
'file_test',
|
||||
'file_validator_test',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$filename = 'test.bbb';
|
||||
vfsStream::newFile($filename)
|
||||
->at($this->vfsRoot)
|
||||
->withContent('test');
|
||||
|
||||
$request = new Request();
|
||||
$request->files->set('files', [
|
||||
'file' => new UploadedFile(
|
||||
path: vfsStream::url("root/$filename"),
|
||||
originalName: $filename,
|
||||
mimeType: 'text/plain',
|
||||
error: \UPLOAD_ERR_OK,
|
||||
test: TRUE
|
||||
),
|
||||
]);
|
||||
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push($request);
|
||||
|
||||
$this->container->set('request_stack', $requestStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests file_save_upload() with empty extensions.
|
||||
*/
|
||||
public function testFileSaveUploadEmptyExtensions(): void {
|
||||
// Allow all extensions.
|
||||
$validators = ['FileExtension' => []];
|
||||
$files = file_save_upload('file', $validators);
|
||||
$this->assertCount(1, $files);
|
||||
$file = $files[0];
|
||||
// @todo work out why move_uploaded_file() is failing.
|
||||
$this->assertFalse($file);
|
||||
$messages = \Drupal::messenger()->messagesByType(MessengerInterface::TYPE_ERROR);
|
||||
$this->assertNotEmpty($messages);
|
||||
$this->assertEquals('File upload error. Could not move uploaded file.', $messages[0]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Lock\LockAcquiringException;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\file\Upload\FileUploadHandler;
|
||||
use Drupal\file\Upload\UploadedFileInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
|
||||
/**
|
||||
* Tests the file upload handler.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileUploadHandlerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file', 'file_validator_test'];
|
||||
|
||||
/**
|
||||
* The file upload handler under test.
|
||||
*/
|
||||
protected FileUploadHandler $fileUploadHandler;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->fileUploadHandler = $this->container->get('file.upload_handler');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the lock acquire exception.
|
||||
*/
|
||||
public function testLockAcquireException(): void {
|
||||
|
||||
$lock = $this->createMock(LockBackendInterface::class);
|
||||
$lock->expects($this->once())->method('acquire')->willReturn(FALSE);
|
||||
|
||||
$fileUploadHandler = new FileUploadHandler(
|
||||
$this->container->get('file_system'),
|
||||
$this->container->get('entity_type.manager'),
|
||||
$this->container->get('stream_wrapper_manager'),
|
||||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('file.mime_type.guesser'),
|
||||
$this->container->get('current_user'),
|
||||
$this->container->get('request_stack'),
|
||||
$this->container->get('file.repository'),
|
||||
$this->container->get('file.validator'),
|
||||
$lock,
|
||||
$this->container->get('validation.basic_recursive_validator_factory'),
|
||||
);
|
||||
|
||||
$file_name = $this->randomMachineName();
|
||||
$file_info = $this->createMock(UploadedFileInterface::class);
|
||||
$file_info->expects($this->once())->method('getClientOriginalName')->willReturn($file_name);
|
||||
$file_info->expects($this->once())->method('validate')->willReturn(new ConstraintViolationList());
|
||||
|
||||
$this->expectException(LockAcquiringException::class);
|
||||
$this->expectExceptionMessage(sprintf('File "temporary://%s" is already locked for writing.', $file_name));
|
||||
|
||||
$fileUploadHandler->handleFileUpload(uploadedFile: $file_info);
|
||||
}
|
||||
|
||||
}
|
||||
42
web/core/modules/file/tests/src/Kernel/FileUriItemTest.php
Normal file
42
web/core/modules/file/tests/src/Kernel/FileUriItemTest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$uri = 'public://druplicon.txt';
|
||||
|
||||
// Create a new file entity.
|
||||
$file = File::create([
|
||||
'uid' => 1,
|
||||
'filename' => 'druplicon.txt',
|
||||
'uri' => $uri,
|
||||
'filemime' => 'text/plain',
|
||||
]);
|
||||
$file->setPermanent();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
31
web/core/modules/file/tests/src/Kernel/FileUrlTest.php
Normal file
31
web/core/modules/file/tests/src/Kernel/FileUrlTest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
|
||||
/**
|
||||
* Tests the file URL.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileUrlTest extends FileManagedUnitTestBase {
|
||||
|
||||
/**
|
||||
* Tests public files with a different host name from settings.
|
||||
*/
|
||||
public function testFilesUrlWithDifferentHostName(): void {
|
||||
$test_base_url = 'http://www.example.com/cdn';
|
||||
$this->setSetting('file_public_base_url', $test_base_url);
|
||||
$filepath = \Drupal::service('file_system')->createFilename('test.txt', '');
|
||||
$directory_uri = 'public://' . dirname($filepath);
|
||||
\Drupal::service('file_system')->prepareDirectory($directory_uri, FileSystemInterface::CREATE_DIRECTORY);
|
||||
$file = $this->createFile($filepath, NULL, 'public');
|
||||
$url = $file->createFileUrl(FALSE);
|
||||
$expected_url = $test_base_url . '/' . basename($filepath);
|
||||
$this->assertSame($url, $expected_url);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel\Formatter;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the default file formatter.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FileEntityFormatterTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['file', 'user', 'file_test'];
|
||||
|
||||
/**
|
||||
* The files.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $files;
|
||||
|
||||
/**
|
||||
* The file URL generator.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileUrlGeneratorInterface
|
||||
*/
|
||||
protected $fileUrlGenerator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->fileUrlGenerator = $this->container->get('file_url_generator');
|
||||
$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(): void {
|
||||
$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->assertEquals('file.png', $build['#title']);
|
||||
$this->assertEquals($this->fileUrlGenerator->generate('public://file.png'), $build['#url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the file_link field formatter.
|
||||
*/
|
||||
public function testFormatterFileUri(): void {
|
||||
$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->assertEquals('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->assertEquals($this->fileUrlGenerator->generateAbsoluteString('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->assertEquals($this->fileUrlGenerator->generateAbsoluteString('public://file.png'), $build['#title']);
|
||||
$this->assertEquals($this->fileUrlGenerator->generate('public://file.png'), $build['#url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the file_extension field formatter.
|
||||
*/
|
||||
public function testFormatterFileExtension(): void {
|
||||
$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->assertEquals($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->assertEquals($expected[$i], $build['filename'][0]['#markup']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the file_extension field formatter.
|
||||
*/
|
||||
public function testFormatterFileMime(): void {
|
||||
$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 $file) {
|
||||
$build = $entity_display->build($file);
|
||||
$this->assertEquals('image__file_icon', $build['filemime'][0]['#theme']);
|
||||
$this->assertEquals(spl_object_hash($file), spl_object_hash($build['filemime'][0]['#file']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the file_size field formatter.
|
||||
*/
|
||||
public function testFormatterFileSize(): void {
|
||||
$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->assertEquals($expected[$i], $build['filesize'][0]['#markup']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the file_link field formatter using a query string.
|
||||
*/
|
||||
public function testFormatterFileLinkWithQueryString(): void {
|
||||
$file = File::create([
|
||||
'uri' => 'dummy-external-readonly://file-query-string?foo=bar',
|
||||
'filename' => 'file-query-string',
|
||||
]);
|
||||
$file->save();
|
||||
$file_link = [
|
||||
'#theme' => 'file_link',
|
||||
'#file' => $file,
|
||||
];
|
||||
|
||||
$output = (string) \Drupal::service('renderer')->renderRoot($file_link);
|
||||
$this->assertStringContainsString($this->fileUrlGenerator->generate('dummy-external-readonly://file-query-string?foo=bar')->toUriString(), $output);
|
||||
}
|
||||
|
||||
}
|
||||
103
web/core/modules/file/tests/src/Kernel/LoadTest.php
Normal file
103
web/core/modules/file/tests/src/Kernel/LoadTest.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\file_test\FileTestHelper;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\file\Entity\File::load().
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class LoadTest extends FileManagedUnitTestBase {
|
||||
|
||||
/**
|
||||
* Try to load a non-existent file by fid.
|
||||
*/
|
||||
public function testLoadMissingFid(): void {
|
||||
$this->assertNull(File::load(-1), 'Try to load an invalid fid fails.');
|
||||
$this->assertFileHooksCalled([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load a non-existent file by URI.
|
||||
*/
|
||||
public function testLoadMissingFilepath(): void {
|
||||
$files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['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(): void {
|
||||
$files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['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(): void {
|
||||
// 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->assertIsObject($by_fid_file);
|
||||
$this->assertEquals($file->id(), $by_fid_file->id(), 'Loading by fid got the same fid.');
|
||||
$this->assertEquals($file->getFileUri(), $by_fid_file->getFileUri(), 'Loading by fid got the correct filepath.');
|
||||
$this->assertEquals($file->getFilename(), $by_fid_file->getFilename(), 'Loading by fid got the correct filename.');
|
||||
$this->assertEquals($file->getMimeType(), $by_fid_file->getMimeType(), 'Loading by fid got the correct MIME type.');
|
||||
$this->assertEquals($file->isPermanent(), $by_fid_file->isPermanent(), 'Loading by fid got the correct status.');
|
||||
$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(): void {
|
||||
// Create a new file entity.
|
||||
$file = $this->createFile('druplicon.txt', NULL, 'public');
|
||||
|
||||
// Load by path.
|
||||
FileTestHelper::reset();
|
||||
$by_path_files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $file->getFileUri()]);
|
||||
$this->assertFileHookCalled('load');
|
||||
$this->assertCount(1, $by_path_files, '\Drupal::entityTypeManager()->getStorage(\'file\')->loadByProperties() 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->assertEquals($file->id(), $by_path_file->id(), 'Loading by filepath got the correct fid.');
|
||||
|
||||
// Load by fid.
|
||||
FileTestHelper::reset();
|
||||
$by_fid_files = File::loadMultiple([$file->id()]);
|
||||
$this->assertFileHooksCalled([]);
|
||||
$this->assertCount(1, $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->assertEquals($file->getFileUri(), $by_fid_file->getFileUri(), 'Loading by fid got the correct filepath.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single file and ensure that the correct values are returned.
|
||||
*/
|
||||
public function testUuidValues(): void {
|
||||
// Create a new file entity from scratch so we know the values.
|
||||
$file = $this->createFile('druplicon.txt', NULL, 'public');
|
||||
$file->save();
|
||||
FileTestHelper::reset();
|
||||
|
||||
$by_uuid_file = \Drupal::service('entity.repository')->loadEntityByUuid('file', $file->uuid());
|
||||
$this->assertFileHookCalled('load');
|
||||
$this->assertInstanceOf(FileInterface::class, $by_uuid_file);
|
||||
$this->assertEquals($file->id(), $by_uuid_file->id(), 'Loading by UUID got the same fid.');
|
||||
$this->assertTrue($by_uuid_file->file_test['loaded'], 'file_test_file_load() was able to modify the file during load.');
|
||||
}
|
||||
|
||||
}
|
||||
59
web/core/modules/file/tests/src/Kernel/ManagedFileTest.php
Normal file
59
web/core/modules/file/tests/src/Kernel/ManagedFileTest.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Managed file element test.
|
||||
*
|
||||
* @group file
|
||||
*
|
||||
* @see \Drupal\file\Element\ManagedFile
|
||||
*/
|
||||
class ManagedFileTest extends FileManagedUnitTestBase implements FormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'form_test_managed_file';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['managed_file'] = [
|
||||
'#type' => 'managed_file',
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {}
|
||||
|
||||
/**
|
||||
* Tests that managed file elements can be programmatically submitted.
|
||||
*/
|
||||
public function testManagedFileElement(): void {
|
||||
$form_state = new FormState();
|
||||
$values['managed_file'] = NULL;
|
||||
$form_state->setValues($values);
|
||||
$this->container->get('form_builder')->submitForm($this, $form_state);
|
||||
// Should submit without any errors.
|
||||
$this->assertEquals(0, count($form_state->getErrors()));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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}
|
||||
*/
|
||||
protected static $modules = ['file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of file stubs.
|
||||
*/
|
||||
public function testStub(): void {
|
||||
$this->performStubTest('file');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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/tests/fixtures';
|
||||
$source['constants']['source_base_path'] = $this->root . '/';
|
||||
$migration->set('source', $source);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user