Core and composer updates
This commit is contained in:
parent
a82634bb98
commit
62cac30480
1118 changed files with 21770 additions and 6306 deletions
|
@ -904,6 +904,14 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
// If we made it this far it's safe to record this file in the database.
|
||||
$file->save();
|
||||
$files[$i] = $file;
|
||||
// Allow an anonymous user who creates a non-public file to see it. See
|
||||
// \Drupal\file\FileAccessControlHandler::checkAccess().
|
||||
if ($user->isAnonymous() && $destination_scheme !== 'public') {
|
||||
$session = \Drupal::request()->getSession();
|
||||
$allowed_temp_files = $session->get('anonymous_allowed_file_ids', []);
|
||||
$allowed_temp_files[$file->id()] = $file->id();
|
||||
$session->set('anonymous_allowed_file_ids', $allowed_temp_files);
|
||||
}
|
||||
}
|
||||
|
||||
// Add files to the cache.
|
||||
|
|
|
@ -7,7 +7,7 @@ migration_tags:
|
|||
source:
|
||||
plugin: d6_file
|
||||
constants:
|
||||
# source_base_path must be set by the tool configuring this migration. It
|
||||
# The tool configuring this migration must set source_base_path. It
|
||||
# represents the fully qualified path relative to which URIs in the files
|
||||
# table are specified, and must end with a /. See source_full_path
|
||||
# configuration in this migration's process pipeline as an example.
|
||||
|
|
|
@ -13,7 +13,7 @@ process:
|
|||
source: upload
|
||||
process:
|
||||
target_id:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: d6_file
|
||||
source: fid
|
||||
display: list
|
||||
|
|
|
@ -14,7 +14,14 @@ source:
|
|||
settings: {}
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
bundle: node_type
|
||||
bundle:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_node_type
|
||||
source: node_type
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
view_mode: 'constants/view_mode'
|
||||
field_name: 'constants/name'
|
||||
type: 'constants/type'
|
||||
|
|
|
@ -15,7 +15,14 @@ source:
|
|||
progress_indicator: throbber
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
bundle: node_type
|
||||
bundle:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_node_type
|
||||
source: node_type
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
field_name: 'constants/name'
|
||||
form_mode: 'constants/form_mode'
|
||||
type: 'constants/type'
|
||||
|
|
|
@ -13,7 +13,7 @@ process:
|
|||
entity_type: 'constants/entity_type'
|
||||
bundle:
|
||||
-
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: d6_node_type
|
||||
source: node_type
|
||||
-
|
||||
|
|
|
@ -7,8 +7,8 @@ migration_tags:
|
|||
source:
|
||||
plugin: d7_file
|
||||
constants:
|
||||
# source_base_path must be set by the tool configuring this migration. It
|
||||
# represents the fully qualified path relative to which uris in the files
|
||||
# The tool configuring this migration must set source_base_path. It
|
||||
# represents the fully qualified path relative to which URIs in the files
|
||||
# table are specified, and must end with a /. See source_full_path
|
||||
# configuration in this migration's process pipeline as an example.
|
||||
source_base_path: ''
|
||||
|
@ -32,8 +32,7 @@ process:
|
|||
- '@source_full_path'
|
||||
- uri
|
||||
filemime: filemime
|
||||
# filesize is dynamically computed when file entities are saved, so there is
|
||||
# no point in migrating it.
|
||||
# No need to migrate filesize, it is computed when file entities are saved.
|
||||
# filesize: filesize
|
||||
status: status
|
||||
# Drupal 7 didn't keep track of the file's creation or update time -- all it
|
||||
|
|
|
@ -40,8 +40,20 @@ class FileAccessControlHandler extends EntityAccessControlHandler {
|
|||
}
|
||||
elseif ($entity->getOwnerId() == $account->id()) {
|
||||
// This case handles new nodes, or detached files. The user who uploaded
|
||||
// the file can always access if it's not yet used.
|
||||
return AccessResult::allowed();
|
||||
// the file can access it even if it's not yet used.
|
||||
if ($account->isAnonymous()) {
|
||||
// For anonymous users, only the browser session that uploaded the
|
||||
// file is positively allowed access to it. See file_save_upload().
|
||||
// @todo Implement \Drupal\Core\Entity\EntityHandlerInterface so that
|
||||
// services can be more properly injected.
|
||||
$allowed_fids = \Drupal::service('session')->get('anonymous_allowed_file_ids', []);
|
||||
if (!empty($allowed_fids[$entity->id()])) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
else {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,9 +91,24 @@ class FileAccessControlHandler extends EntityAccessControlHandler {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
|
||||
// No user can edit the status of a file. Prevents saving a new file as
|
||||
// persistent before even validating it.
|
||||
if ($field_definition->getName() === 'status' && $operation === 'edit') {
|
||||
// Deny access to fields that should only be set on file creation, and
|
||||
// "status" which should only be changed based on a file's usage.
|
||||
$create_only_fields = [
|
||||
'uri',
|
||||
'filemime',
|
||||
'filesize',
|
||||
];
|
||||
// The operation is 'edit' when the entity is being created or updated.
|
||||
// Determine if the entity is being updated by checking if it is new.
|
||||
$field_name = $field_definition->getName();
|
||||
if ($operation === 'edit' && $items && ($entity = $items->getEntity()) && !$entity->isNew() && in_array($field_name, $create_only_fields, TRUE)) {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
// Regardless of whether the entity exists access should be denied to the
|
||||
// status field as this is managed via other APIs, for example:
|
||||
// - \Drupal\file\FileUsage\FileUsageBase::add()
|
||||
// - \Drupal\file\Plugin\EntityReferenceSelection\FileSelection::createNewEntity()
|
||||
if ($operation === 'edit' && $field_name === 'status') {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
return parent::checkFieldAccess($operation, $field_definition, $account, $items);
|
||||
|
|
|
@ -25,8 +25,8 @@ interface FileUsageInterface {
|
|||
* The name of the module using the file.
|
||||
* @param string $type
|
||||
* The type of the object that contains the referenced file.
|
||||
* @param int $id
|
||||
* The unique, numeric ID of the object containing the referenced file.
|
||||
* @param string $id
|
||||
* The unique ID of the object containing the referenced file.
|
||||
* @param int $count
|
||||
* (optional) The number of references to add to the object. Defaults to 1.
|
||||
*/
|
||||
|
@ -43,10 +43,10 @@ interface FileUsageInterface {
|
|||
* (optional) The type of the object that contains the referenced file. May
|
||||
* be omitted if all module references to a file are being deleted. Defaults
|
||||
* to NULL.
|
||||
* @param int $id
|
||||
* (optional) The unique, numeric ID of the object containing the referenced
|
||||
* file. May be omitted if all module references to a file are being
|
||||
* deleted. Defaults to NULL.
|
||||
* @param string $id
|
||||
* (optional) The unique ID of the object containing the referenced file.
|
||||
* May be omitted if all module references to a file are being deleted.
|
||||
* Defaults to NULL.
|
||||
* @param int $count
|
||||
* (optional) The number of references to delete from the object. Defaults
|
||||
* to 1. Zero may be specified to delete all references to the file within a
|
||||
|
|
|
@ -6,6 +6,7 @@ use Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraint;
|
|||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Uploads a test to a private node and checks access.
|
||||
|
@ -110,6 +111,118 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalLogin($account);
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that access is denied for another user to the temporary file.');
|
||||
|
||||
// As an anonymous user, create a temporary file with no references and
|
||||
// confirm that only the session that uploaded it may view it.
|
||||
$this->drupalLogout();
|
||||
user_role_change_permissions(
|
||||
RoleInterface::ANONYMOUS_ID,
|
||||
[
|
||||
"create $type_name content" => TRUE,
|
||||
'access content' => TRUE,
|
||||
]
|
||||
);
|
||||
$test_file = $this->getTestFile('text');
|
||||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = ['files[' . $field_name . '_0]' => drupal_realpath($test_file->getFileUri())];
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
/** @var \Drupal\file\FileStorageInterface $file_storage */
|
||||
$file_storage = $this->container->get('entity.manager')->getStorage('file');
|
||||
$files = $file_storage->loadByProperties(['uid' => 0]);
|
||||
$this->assertEqual(1, count($files), 'Loaded one anonymous file.');
|
||||
$file = end($files);
|
||||
$this->assertTrue($file->isTemporary(), 'File is temporary.');
|
||||
$usage = $this->container->get('file.usage')->listUsage($file);
|
||||
$this->assertFalse($usage, 'No file usage found.');
|
||||
$file_url = file_create_url($file->getFileUri());
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.');
|
||||
|
||||
// As an anonymous user, create a permanent file, then remove all
|
||||
// references to the file (so that it becomes temporary again) and confirm
|
||||
// that only the session that uploaded it may view it.
|
||||
$test_file = $this->getTestFile('text');
|
||||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$file_id = $new_node->{$field_name}->target_id;
|
||||
$file = File::load($file_id);
|
||||
$this->assertTrue($file->isPermanent(), 'File is permanent.');
|
||||
// Remove the reference to this file.
|
||||
$new_node->{$field_name} = [];
|
||||
$new_node->save();
|
||||
$file = File::load($file_id);
|
||||
$this->assertTrue($file->isTemporary(), 'File is temporary.');
|
||||
$usage = $this->container->get('file.usage')->listUsage($file);
|
||||
$this->assertFalse($usage, 'No file usage found.');
|
||||
$file_url = file_create_url($file->getFileUri());
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the file whose references were removed.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the file whose references were removed.');
|
||||
|
||||
// As an anonymous user, create a permanent file that is referenced by a
|
||||
// published node and confirm that all anonymous users may view it.
|
||||
$test_file = $this->getTestFile('text');
|
||||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$file = File::load($new_node->{$field_name}->target_id);
|
||||
$this->assertTrue($file->isPermanent(), 'File is permanent.');
|
||||
$usage = $this->container->get('file.usage')->listUsage($file);
|
||||
$this->assertTrue($usage, 'File usage found.');
|
||||
$file_url = file_create_url($file->getFileUri());
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.');
|
||||
|
||||
// As an anonymous user, create a permanent file that is referenced by an
|
||||
// unpublished node and confirm that no anonymous users may view it (even
|
||||
// the session that uploaded the file) because they cannot view the
|
||||
// unpublished node.
|
||||
$test_file = $this->getTestFile('text');
|
||||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$new_node->setPublished(FALSE);
|
||||
$new_node->save();
|
||||
$file = File::load($new_node->{$field_name}->target_id);
|
||||
$this->assertTrue($file->isPermanent(), 'File is permanent.');
|
||||
$usage = $this->container->get('file.usage')->listUsage($file);
|
||||
$this->assertTrue($usage, 'File usage found.');
|
||||
$file_url = file_create_url($file->getFileUri());
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -85,11 +85,30 @@ class AccessTest extends KernelTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests that the status field is not editable.
|
||||
* Tests file entity field access.
|
||||
*
|
||||
* @see \Drupal\file\FileAccessControlHandler::checkFieldAccess()
|
||||
*/
|
||||
public function testStatusFieldIsNotEditable() {
|
||||
public function testCheckFieldAccess() {
|
||||
\Drupal::currentUser()->setAccount($this->user1);
|
||||
$this->assertFalse($this->file->get('status')->access('edit'));
|
||||
/** @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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,6 +45,7 @@ class FileItemValidationTest extends KernelTestBase {
|
|||
'status' => 1,
|
||||
]);
|
||||
$this->user->save();
|
||||
$this->container->get('current_user')->setAccount($this->user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,6 +86,7 @@ class FileItemValidationTest extends KernelTestBase {
|
|||
// Test for max filesize.
|
||||
$file = File::create([
|
||||
'uri' => 'vfs://drupal_root/sites/default/files/test.txt',
|
||||
'uid' => $this->user->id(),
|
||||
]);
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
|
|
|
@ -12,19 +12,25 @@ use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
|||
*/
|
||||
class MigrateUploadEntityDisplayTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['menu_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->migrateFields();
|
||||
$this->executeMigration('d6_upload_entity_display');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Drupal 6 upload settings to Drupal 8 entity display migration.
|
||||
* Tests Drupal 6 upload settings to Drupal 8 entity display migration.
|
||||
*/
|
||||
public function testUploadEntityDisplay() {
|
||||
$this->executeMigration('d6_upload_entity_display');
|
||||
|
||||
$display = EntityViewDisplay::load('node.page.default');
|
||||
$component = $display->getComponent('upload');
|
||||
$this->assertIdentical('file_default', $component['type']);
|
||||
|
@ -41,4 +47,23 @@ class MigrateUploadEntityDisplayTest extends MigrateDrupal6TestBase {
|
|||
$this->assertIdentical(['node', 'page', 'default', 'upload'], $this->getMigration('d6_upload_entity_display')->getIdMap()->lookupDestinationID(['page']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that entity displays are ignored appropriately.
|
||||
*
|
||||
* Entity displays should be ignored when they belong to node types which
|
||||
* were not migrated.
|
||||
*/
|
||||
public function testSkipNonExistentNodeType() {
|
||||
// The "story" node type is migrated by d6_node_type but we need to pretend
|
||||
// that it didn't occur, so record that in the map table.
|
||||
$this->mockFailure('d6_node_type', ['type' => 'story']);
|
||||
|
||||
// d6_upload_entity_display should skip over the "story" node type config
|
||||
// because, according to the map table, it didn't occur.
|
||||
$migration = $this->getMigration('d6_upload_entity_display');
|
||||
|
||||
$this->executeMigration($migration);
|
||||
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['node_type' => 'story'])[0][0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,19 +12,25 @@ use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
|||
*/
|
||||
class MigrateUploadEntityFormDisplayTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['menu_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->migrateFields();
|
||||
$this->executeMigration('d6_upload_entity_form_display');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Drupal 6 upload settings to Drupal 8 entity form display migration.
|
||||
* Tests Drupal 6 upload settings to Drupal 8 entity form display migration.
|
||||
*/
|
||||
public function testUploadEntityFormDisplay() {
|
||||
$this->executeMigration('d6_upload_entity_form_display');
|
||||
|
||||
$display = EntityFormDisplay::load('node.page.default');
|
||||
$component = $display->getComponent('upload');
|
||||
$this->assertIdentical('file_generic', $component['type']);
|
||||
|
@ -41,4 +47,23 @@ class MigrateUploadEntityFormDisplayTest extends MigrateDrupal6TestBase {
|
|||
$this->assertIdentical(['node', 'page', 'default', 'upload'], $this->getMigration('d6_upload_entity_form_display')->getIdMap()->lookupDestinationID(['page']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that entity displays are ignored appropriately.
|
||||
*
|
||||
* Entity displays should be ignored when they belong to node types which
|
||||
* were not migrated.
|
||||
*/
|
||||
public function testSkipNonExistentNodeType() {
|
||||
// The "story" node type is migrated by d6_node_type but we need to pretend
|
||||
// that it didn't occur, so record that in the map table.
|
||||
$this->mockFailure('d6_node_type', ['type' => 'story']);
|
||||
|
||||
// d6_upload_entity_form_display should skip over the "story" node type
|
||||
// config because according to the map table, it didn't occur.
|
||||
$migration = $this->getMigration('d6_upload_entity_form_display');
|
||||
|
||||
$this->executeMigration($migration);
|
||||
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['node_type' => 'story'])[0][0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,11 @@ use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
|||
*/
|
||||
class MigrateUploadFieldTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['menu_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,11 @@ use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
|||
*/
|
||||
class MigrateUploadInstanceTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['menu_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -13,6 +13,11 @@ use Drupal\node\Entity\Node;
|
|||
*/
|
||||
class MigrateUploadTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['menu_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
Reference in a new issue