Issue #3382581 by phenaproxima, borisson_, larowlan, Wim Leers, quietone: Add new `EntityBundleExists` constraint
parent
d75377535a
commit
89ec081a50
|
@ -499,6 +499,8 @@ field_config_base:
|
|||
bundle:
|
||||
type: string
|
||||
label: 'Bundle'
|
||||
constraints:
|
||||
EntityBundleExists: '%parent.entity_type'
|
||||
label:
|
||||
type: required_label
|
||||
label: 'Label'
|
||||
|
|
|
@ -54,6 +54,8 @@ core.entity_view_display.*.*.*:
|
|||
bundle:
|
||||
type: string
|
||||
label: 'Bundle'
|
||||
constraints:
|
||||
EntityBundleExists: '%parent.targetEntityType'
|
||||
mode:
|
||||
type: string
|
||||
label: 'View or form mode machine name'
|
||||
|
@ -115,6 +117,8 @@ core.entity_form_display.*.*.*:
|
|||
bundle:
|
||||
type: string
|
||||
label: 'Bundle'
|
||||
constraints:
|
||||
EntityBundleExists: '%parent.targetEntityType'
|
||||
mode:
|
||||
type: string
|
||||
label: 'View or form mode machine name'
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* Provides helper methods for resolving config schema types.
|
||||
*
|
||||
* @internal
|
||||
* This is an internal part of the config schema system and may be changed or
|
||||
* removed any time. External code should not interact with this class.
|
||||
*/
|
||||
class TypeResolver {
|
||||
|
||||
/**
|
||||
* Replaces dynamic type expressions in configuration type.
|
||||
*
|
||||
* The configuration type name may contain one or more expressions to be
|
||||
* replaced, enclosed in square brackets like '[name]' or '[%parent.id]' and
|
||||
* will follow the replacement rules defined by the resolveExpression()
|
||||
* method.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration type, potentially with expressions in square brackets.
|
||||
* @param array $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
* @return string
|
||||
* Configuration type name with all expressions resolved.
|
||||
*/
|
||||
public static function resolveDynamicTypeName(string $name, mixed $data): string {
|
||||
if (preg_match_all("/\[(.*)\]/U", $name, $matches)) {
|
||||
// Build our list of '[value]' => replacement.
|
||||
$replace = [];
|
||||
foreach (array_combine($matches[0], $matches[1]) as $key => $value) {
|
||||
$replace[$key] = self::resolveExpression($value, $data);
|
||||
}
|
||||
return strtr($name, $replace);
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a dynamic type expression using configuration data.
|
||||
*
|
||||
* Dynamic type names are nested configuration keys containing expressions to
|
||||
* be replaced by the value at the property path that the expression is
|
||||
* pointing at. The expression may contain the following special strings:
|
||||
* - '%key', will be replaced by the element's key.
|
||||
* - '%parent', to reference the parent element.
|
||||
* - '%type', to reference the schema definition type. Can only be used in
|
||||
* combination with %parent.
|
||||
*
|
||||
* There may be nested configuration keys separated by dots or more complex
|
||||
* patterns like '%parent.name' which references the 'name' value of the
|
||||
* parent element.
|
||||
*
|
||||
* Example expressions:
|
||||
* - 'name.subkey', indicates a nested value of the current element.
|
||||
* - '%parent.name', will be replaced by the 'name' value of the parent.
|
||||
* - '%parent.%key', will be replaced by the parent element's key.
|
||||
* - '%parent.%type', will be replaced by the schema type of the parent.
|
||||
* - '%parent.%parent.%type', will be replaced by the schema type of the
|
||||
* parent's parent.
|
||||
*
|
||||
* @param string $expression
|
||||
* Expression to be resolved.
|
||||
* @param array|\Drupal\Core\TypedData\TypedDataInterface $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
* @return string
|
||||
* The value the expression resolves to, or the given expression if it
|
||||
* cannot be resolved.
|
||||
*
|
||||
* @todo Validate the expression in https://www.drupal.org/project/drupal/issues/3392903
|
||||
*/
|
||||
public static function resolveExpression(string $expression, array|TypedDataInterface $data): string {
|
||||
if ($data instanceof TypedDataInterface) {
|
||||
$data = [
|
||||
'%parent' => $data->getParent(),
|
||||
'%key' => $data->getName(),
|
||||
'%type' => $data->getDataDefinition()->getDataType(),
|
||||
];
|
||||
}
|
||||
|
||||
$parts = explode('.', $expression);
|
||||
// Process each value part, one at a time.
|
||||
while ($name = array_shift($parts)) {
|
||||
if (!is_array($data) || !isset($data[$name])) {
|
||||
// Key not found, return original value
|
||||
return $expression;
|
||||
}
|
||||
if (!$parts) {
|
||||
$expression = $data[$name];
|
||||
if (is_bool($expression)) {
|
||||
$expression = (int) $expression;
|
||||
}
|
||||
// If no more parts left, this is the final property.
|
||||
return (string) $expression;
|
||||
}
|
||||
// Get nested value and continue processing.
|
||||
if ($name == '%parent') {
|
||||
/** @var \Drupal\Core\Config\Schema\ArrayElement $parent */
|
||||
// Switch replacement values with values from the parent.
|
||||
$parent = $data['%parent'];
|
||||
$data = $parent->getValue();
|
||||
$data['%type'] = $parent->getDataDefinition()->getDataType();
|
||||
// The special %parent and %key values now need to point one level up.
|
||||
if ($new_parent = $parent->getParent()) {
|
||||
$data['%parent'] = $new_parent;
|
||||
$data['%key'] = $new_parent->getName();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$data = $data[$name];
|
||||
}
|
||||
// Return the original value
|
||||
return $expression;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ use Drupal\Component\Utility\NestedArray;
|
|||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
|
||||
use Drupal\Core\Config\Schema\ConfigSchemaDiscovery;
|
||||
use Drupal\Core\Config\Schema\TypeResolver;
|
||||
use Drupal\Core\Config\Schema\SequenceDataDefinition;
|
||||
use Drupal\Core\DependencyInjection\ClassResolverInterface;
|
||||
use Drupal\Core\Config\Schema\Undefined;
|
||||
|
@ -108,7 +109,7 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
if (isset($name)) {
|
||||
$replace['%key'] = $name;
|
||||
}
|
||||
$type = $this->resolveDynamicTypeName($type, $replace);
|
||||
$type = TypeResolver::resolveDynamicTypeName($type, $replace);
|
||||
// Remove the type from the definition so that it is replaced with the
|
||||
// concrete type from schema definitions.
|
||||
unset($definition['type']);
|
||||
|
@ -288,7 +289,7 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
|
||||
// Replace dynamic portions of the definition type.
|
||||
if (!empty($replacements) && strpos($definition['type'], ']')) {
|
||||
$sub_type = $this->determineType($this->resolveDynamicTypeName($definition['type'], $replacements), $definitions);
|
||||
$sub_type = $this->determineType(TypeResolver::resolveDynamicTypeName($definition['type'], $replacements), $definitions);
|
||||
$sub_definition = $definitions[$sub_type];
|
||||
if (isset($definitions[$sub_type]['type'])) {
|
||||
$sub_merge = $this->getDefinition($definitions[$sub_type]['type'], $exception_on_invalid);
|
||||
|
@ -395,45 +396,8 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
/**
|
||||
* Replaces dynamic type expressions in configuration type.
|
||||
*
|
||||
* The configuration type name may contain one or more expressions to be
|
||||
* replaced, enclosed in square brackets like '[name]' or '[%parent.id]' and
|
||||
* will follow the replacement rules defined by the resolveExpression()
|
||||
* method.
|
||||
*
|
||||
* @param string $type
|
||||
* Configuration type, potentially with expressions in square brackets.
|
||||
* @param array $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
* @return string
|
||||
* Configuration type name with all expressions resolved.
|
||||
*/
|
||||
protected function resolveDynamicTypeName(string $type, array $data): string {
|
||||
// Parse the expressions in the dynamic type, if any.
|
||||
if (preg_match_all("/\[(.*)\]/U", $type, $matches)) {
|
||||
// Build our list of '[value]' => replacement.
|
||||
$replace = [];
|
||||
foreach (array_combine($matches[0], $matches[1]) as $key => $value) {
|
||||
$replace[$key] = $this->resolveExpression($value, $data);
|
||||
}
|
||||
return strtr($type, $replace);
|
||||
}
|
||||
else {
|
||||
// No expressions: nothing to resolve.
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces dynamic type expressions in configuration type.
|
||||
*
|
||||
* The configuration type name may contain one or more expressions to be
|
||||
* replaced, enclosed in square brackets like '[name]' or '[%parent.id]' and
|
||||
* will follow the replacement rules defined by the resolveExpression()
|
||||
* method.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration type, potentially with expressions in square brackets.
|
||||
* Configuration type, potentially with expressions in square brackets.f
|
||||
* @param array $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
|
@ -441,113 +405,19 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
* Configuration type name with all expressions resolved.
|
||||
*
|
||||
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
|
||||
* ::resolveDynamicTypeName() instead.
|
||||
* \Drupal\Core\Config\Schema\TypeResolver::resolveDynamicTypeName::resolveDynamicTypeName()
|
||||
* instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3408266
|
||||
*/
|
||||
protected function replaceName($name, $data) {
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use ::resolveDynamicTypeName() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED);
|
||||
return $this->resolveDynamicTypeName($name, $data);
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\Core\Config\Schema\TypeResolver::resolveDynamicTypeName() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED);
|
||||
return TypeResolver::resolveDynamicTypeName($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a dynamic type expression using configuration data.
|
||||
*
|
||||
* Dynamic type names are nested configuration keys containing expressions to
|
||||
* be replaced by the value at the property path that the expression is
|
||||
* pointing at. The expression may contain the following special strings:
|
||||
* - '%key', will be replaced by the element's key.
|
||||
* - '%parent', to reference the parent element.
|
||||
* - '%type', to reference the schema definition type. Can only be used in
|
||||
* combination with %parent.
|
||||
*
|
||||
* There may be nested configuration keys separated by dots or more complex
|
||||
* patterns like '%parent.name' which references the 'name' value of the
|
||||
* parent element.
|
||||
*
|
||||
* Example expressions:
|
||||
* - 'name.subkey', indicates a nested value of the current element.
|
||||
* - '%parent.name', will be replaced by the 'name' value of the parent.
|
||||
* - '%parent.%key', will be replaced by the parent element's key.
|
||||
* - '%parent.%type', will be replaced by the schema type of the parent.
|
||||
* - '%parent.%parent.%type', will be replaced by the schema type of the
|
||||
* parent's parent.
|
||||
*
|
||||
* @param string $expression
|
||||
* Expression to be resolved.
|
||||
* @param array $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
* @return string
|
||||
* The value the expression resolves to, or the given expression if it
|
||||
* cannot be resolved.
|
||||
*
|
||||
* @todo Validate the expression in https://www.drupal.org/project/drupal/issues/3392903
|
||||
*/
|
||||
protected function resolveExpression(string $expression, array $data): string {
|
||||
assert(!str_contains($expression, '[') && !str_contains($expression, ']'));
|
||||
$parts = explode('.', $expression);
|
||||
// Process each value part, one at a time.
|
||||
while ($name = array_shift($parts)) {
|
||||
if (!is_array($data) || !isset($data[$name])) {
|
||||
// Key not found, return original value
|
||||
return $expression;
|
||||
}
|
||||
elseif (!$parts) {
|
||||
$expression = $data[$name];
|
||||
if (is_bool($expression)) {
|
||||
$expression = (int) $expression;
|
||||
}
|
||||
// If no more parts left, this is the final property.
|
||||
return (string) $expression;
|
||||
}
|
||||
else {
|
||||
// Get nested value and continue processing.
|
||||
if ($name == '%parent') {
|
||||
/** @var \Drupal\Core\Config\Schema\ArrayElement $parent */
|
||||
// Switch replacement values with values from the parent.
|
||||
$parent = $data['%parent'];
|
||||
$data = $parent->getValue();
|
||||
$data['%type'] = $parent->getDataDefinition()->getDataType();
|
||||
// The special %parent and %key values now need to point one level up.
|
||||
if ($new_parent = $parent->getParent()) {
|
||||
$data['%parent'] = $new_parent;
|
||||
$data['%key'] = $new_parent->getName();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$data = $data[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Satisfy PHPStan, which cannot interpret the loop.
|
||||
return $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a dynamic type expression using configuration data.
|
||||
*
|
||||
* Dynamic type names are nested configuration keys containing expressions to
|
||||
* be replaced by the value at the property path that the expression is
|
||||
* pointing at. The expression may contain the following special strings:
|
||||
* - '%key', will be replaced by the element's key.
|
||||
* - '%parent', to reference the parent element.
|
||||
* - '%type', to reference the schema definition type. Can only be used in
|
||||
* combination with %parent.
|
||||
*
|
||||
* There may be nested configuration keys separated by dots or more complex
|
||||
* patterns like '%parent.name' which references the 'name' value of the
|
||||
* parent element.
|
||||
*
|
||||
* Example expressions:
|
||||
* - 'name.subkey', indicates a nested value of the current element.
|
||||
* - '%parent.name', will be replaced by the 'name' value of the parent.
|
||||
* - '%parent.%key', will be replaced by the parent element's key.
|
||||
* - '%parent.%type', will be replaced by the schema type of the parent.
|
||||
* - '%parent.%parent.%type', will be replaced by the schema type of the
|
||||
* parent's parent.
|
||||
*
|
||||
* @param string $value
|
||||
* Expression to be resolved.
|
||||
* @param array $data
|
||||
|
@ -558,13 +428,14 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
* cannot be resolved.
|
||||
*
|
||||
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
|
||||
* ::resolveExpression() instead.
|
||||
* \Drupal\Core\Config\Schema\TypeResolver::resolveDynamicTypeName::resolveExpression()
|
||||
* instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3408266
|
||||
*/
|
||||
protected function replaceVariable($value, $data) {
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use ::resolveExpression() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED);
|
||||
return $this->resolveExpression($value, $data);
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\Core\Config\Schema\TypeResolver::resolveExpression() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED);
|
||||
return TypeResolver::resolveExpression($value, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -608,4 +479,48 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
return $this->create($data_definition, $config_data, $config_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a dynamic type name.
|
||||
*
|
||||
* @param string $type
|
||||
* Configuration type, potentially with expressions in square brackets.
|
||||
* @param array $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
* @return string
|
||||
* Configuration type name with all expressions resolved.
|
||||
*
|
||||
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
|
||||
* \Drupal\Core\Config\Schema\TypeResolver::resolveDynamicTypeName()
|
||||
* instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3413264
|
||||
*/
|
||||
protected function resolveDynamicTypeName(string $type, array $data): string {
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\Core\Config\Schema\TypeResolver::' . __FUNCTION__ . '() instead. See https://www.drupal.org/node/3413264', E_USER_DEPRECATED);
|
||||
return TypeResolver::resolveDynamicTypeName($type, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a dynamic expression.
|
||||
*
|
||||
* @param string $expression
|
||||
* Expression to be resolved.
|
||||
* @param array|\Drupal\Core\TypedData\TypedDataInterface $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
* @return string
|
||||
* The value the expression resolves to, or the given expression if it
|
||||
* cannot be resolved.
|
||||
*
|
||||
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
|
||||
* \Drupal\Core\Config\Schema\TypeResolver::resolveExpression() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3413264
|
||||
*/
|
||||
protected function resolveExpression(string $expression, array $data): string {
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\Core\Config\Schema\TypeResolver::' . __FUNCTION__ . '() instead. See https://www.drupal.org/node/3413264', E_USER_DEPRECATED);
|
||||
return TypeResolver::resolveExpression($expression, $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ use Symfony\Component\Validator\Constraint;
|
|||
/**
|
||||
* Checks if a value is a valid entity type.
|
||||
*
|
||||
* This differs from the `EntityBundleExists` constraint in that checks that the
|
||||
* validated value is an *entity* of a particular bundle.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "Bundle",
|
||||
* label = @Translation("Bundle", context = "Validation"),
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Checks if a bundle exists on a certain content entity type.
|
||||
*
|
||||
* This differs from the `Bundle` constraint in that checks that the validated
|
||||
* value is the *name of a bundle* of a particular entity type.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "EntityBundleExists",
|
||||
* label = @Translation("Entity bundle exists", context = "Validation"),
|
||||
* type = "entity",
|
||||
* )
|
||||
*/
|
||||
class EntityBundleExistsConstraint extends Constraint {
|
||||
|
||||
/**
|
||||
* The error message if validation fails.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = "The '@bundle' bundle does not exist on the '@entity_type_id' entity type.";
|
||||
|
||||
/**
|
||||
* The entity type ID which should have the given bundle.
|
||||
*
|
||||
* This can contain variable values (e.g., `%parent`) that will be replaced.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Schema\TypeResolver::replaceVariable()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $entityTypeId;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOption() {
|
||||
return 'entityTypeId';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRequiredOptions() {
|
||||
return ['entityTypeId'];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Config\Schema\TypeResolver;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Validates that a bundle exists on a certain content entity type.
|
||||
*/
|
||||
class EntityBundleExistsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* Constructs an EntityBundleExistsConstraintValidator object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundleInfo
|
||||
* The entity type bundle info service.
|
||||
*/
|
||||
public function __construct(private readonly EntityTypeBundleInfoInterface $bundleInfo) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get(EntityTypeBundleInfoInterface::class),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
assert($constraint instanceof EntityBundleExistsConstraint);
|
||||
|
||||
if (!is_string($value)) {
|
||||
throw new UnexpectedTypeException($value, 'string');
|
||||
}
|
||||
// Resolve any dynamic tokens, like %parent, in the entity type ID.
|
||||
$entity_type_id = TypeResolver::resolveDynamicTypeName("[$constraint->entityTypeId]", $this->context->getObject());
|
||||
|
||||
if (!array_key_exists($value, $this->bundleInfo->getBundleInfo($entity_type_id))) {
|
||||
$this->context->addViolation($constraint->message, [
|
||||
'@bundle' => $value,
|
||||
'@entity_type_id' => $entity_type_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -532,6 +532,10 @@ class ContactSitewideTest extends BrowserTestBase {
|
|||
$edit += $third_party_settings;
|
||||
$this->drupalGet('admin/structure/contact/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Ensure the statically cached bundle info is aware of the contact form
|
||||
// that was just created in the UI.
|
||||
$this->container->get('entity_type.bundle.info')->clearCachedBundles();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,6 +54,9 @@ class NodeAccessTest extends ModerationStateTestBase {
|
|||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->createContentTypeFromUi('Moderated content', 'moderated_content', FALSE);
|
||||
// Ensure the statically cached entity bundle info is aware of the content
|
||||
// type that was just created in the UI.
|
||||
$this->container->get('entity_type.bundle.info')->clearCachedBundles();
|
||||
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
|
||||
|
||||
// Add the private field to the node type.
|
||||
|
|
|
@ -66,6 +66,10 @@ class SelectionTest extends BrowserTestBase {
|
|||
$this->nodes[$node->id()] = $node;
|
||||
}
|
||||
|
||||
// Ensure the bundle to which the field is attached actually exists, or we
|
||||
// will get config validation errors.
|
||||
entity_test_create_bundle('test_bundle');
|
||||
|
||||
// Create an entity reference field.
|
||||
$handler_settings = [
|
||||
'view' => [
|
||||
|
|
|
@ -669,6 +669,9 @@ class FormTest extends FieldTestBase {
|
|||
$user = $this->drupalCreateUser(['administer entity_test content']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Ensure that the 'bar' bundle exists, to avoid config validation errors.
|
||||
entity_test_create_bundle('bar', entity_type: 'entity_test_base_field_display');
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'field_name' => 'foo',
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
namespace Drupal\Tests\field\Kernel\Entity;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests validation of field_config entities.
|
||||
|
@ -11,7 +14,14 @@ use Drupal\field\Entity\FieldStorageConfig;
|
|||
* @group field
|
||||
* @group #slow
|
||||
*/
|
||||
class FieldConfigValidationTest extends FieldStorageConfigValidationTest {
|
||||
class FieldConfigValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'node', 'entity_test', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -19,14 +29,14 @@ class FieldConfigValidationTest extends FieldStorageConfigValidationTest {
|
|||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// The field storage was created in the parent method.
|
||||
$field_storage = $this->entity;
|
||||
$this->installConfig('node');
|
||||
$this->createContentType(['type' => 'one']);
|
||||
$this->createContentType(['type' => 'another']);
|
||||
|
||||
$this->entity = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'user',
|
||||
]);
|
||||
$this->entity->save();
|
||||
EntityTestBundle::create(['id' => 'one'])->save();
|
||||
EntityTestBundle::create(['id' => 'another'])->save();
|
||||
|
||||
$this->entity = FieldConfig::loadByName('node', 'one', 'body');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +97,17 @@ class FieldConfigValidationTest extends FieldStorageConfigValidationTest {
|
|||
$this->assertValidationErrors([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the target bundle of the field is checked.
|
||||
*/
|
||||
public function testTargetBundleMustExist(): void {
|
||||
$this->entity->set('bundle', 'nope');
|
||||
$this->assertValidationErrors([
|
||||
'' => "The 'bundle' property cannot be changed.",
|
||||
'bundle' => "The 'nope' bundle does not exist on the 'node' entity type.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -96,7 +117,10 @@ class FieldConfigValidationTest extends FieldStorageConfigValidationTest {
|
|||
// settings from the *old* field_type won't match the config schema for the
|
||||
// settings of the *new* field_type.
|
||||
$this->entity->set('settings', []);
|
||||
parent::testImmutableProperties($valid_values);
|
||||
parent::testImmutableProperties([
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'bundle' => 'another',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,7 @@ class FieldEntitySettingsTest extends KernelTestBase {
|
|||
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'type' => 'integer',
|
||||
'entity_type' => 'entity_test',
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'field_name' => 'test',
|
||||
]);
|
||||
$field = FieldConfig::create([
|
||||
|
@ -95,10 +95,10 @@ class FieldEntitySettingsTest extends KernelTestBase {
|
|||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'test_reference',
|
||||
'type' => 'entity_reference',
|
||||
'entity_type' => 'entity_test',
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'target_type' => 'entity_test',
|
||||
'target_type' => 'entity_test_with_bundle',
|
||||
],
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
@ -111,10 +111,10 @@ class FieldEntitySettingsTest extends KernelTestBase {
|
|||
'handler' => 'default',
|
||||
],
|
||||
]);
|
||||
$this->assertSame('default:entity_test', $field->getSetting('handler'));
|
||||
$this->assertSame('default:entity_test_with_bundle', $field->getSetting('handler'));
|
||||
// If the handler is changed, it should be normalized again on pre-save.
|
||||
$field->setSetting('handler', 'default')->save();
|
||||
$this->assertSame('default:entity_test', $field->getSetting('handler'));
|
||||
$this->assertSame('default:entity_test_with_bundle', $field->getSetting('handler'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -73,6 +73,10 @@ class SelectionTest extends KernelTestBase {
|
|||
$this->nodes[$node->id()] = $node;
|
||||
}
|
||||
|
||||
// Ensure the bundle to which the field is attached actually exists, or we
|
||||
// will get config validation errors.
|
||||
entity_test_create_bundle('test_bundle');
|
||||
|
||||
// Create an entity reference field.
|
||||
$handler_settings = [
|
||||
'view' => [
|
||||
|
|
|
@ -84,8 +84,8 @@ class FieldAttachStorageTest extends FieldKernelTestBase {
|
|||
1 => 'test_bundle_1',
|
||||
2 => 'test_bundle_2',
|
||||
];
|
||||
entity_test_create_bundle($bundles[1]);
|
||||
entity_test_create_bundle($bundles[2]);
|
||||
entity_test_create_bundle($bundles[1], entity_type: $entity_type);
|
||||
entity_test_create_bundle($bundles[2], entity_type: $entity_type);
|
||||
// Define 3 fields:
|
||||
// - field_1 is in bundle_1 and bundle_2,
|
||||
// - field_2 is in bundle_1,
|
||||
|
@ -361,7 +361,12 @@ class FieldAttachStorageTest extends FieldKernelTestBase {
|
|||
$this->assertCount(4, $entity->{$this->fieldTestData->field_name}, 'First field got loaded');
|
||||
$this->assertCount(1, $entity->{$field_name}, 'Second field got loaded');
|
||||
|
||||
// Delete the bundle.
|
||||
// Delete the bundle. The form display has to be deleted first to prevent
|
||||
// schema errors when fields attached to the deleted bundle are themselves
|
||||
// deleted, which triggers an update of the form display.
|
||||
$this->container->get('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $this->fieldTestData->field->getTargetBundle())
|
||||
->delete();
|
||||
entity_test_delete_bundle($this->fieldTestData->field->getTargetBundle(), $entity_type);
|
||||
|
||||
// Verify no data gets loaded
|
||||
|
|
|
@ -26,6 +26,8 @@ class FieldImportChangeTest extends FieldKernelTestBase {
|
|||
* Tests importing an updated field.
|
||||
*/
|
||||
public function testImportChange() {
|
||||
entity_test_create_bundle('test_bundle');
|
||||
|
||||
$this->installConfig(['field_test_config']);
|
||||
$field_storage_id = 'field_test_import';
|
||||
$field_id = "entity_test.entity_test.$field_storage_id";
|
||||
|
|
|
@ -28,6 +28,8 @@ class FieldImportDeleteTest extends FieldKernelTestBase {
|
|||
* Tests deleting field storages and fields as part of config import.
|
||||
*/
|
||||
public function testImportDelete() {
|
||||
entity_test_create_bundle('test_bundle');
|
||||
|
||||
$this->installConfig(['field_test_config']);
|
||||
// At this point there are 5 field configuration objects in the active
|
||||
// storage.
|
||||
|
|
|
@ -117,6 +117,8 @@ language.content_settings.*.*:
|
|||
target_bundle:
|
||||
type: string
|
||||
label: 'Bundle'
|
||||
constraints:
|
||||
EntityBundleExists: '%parent.target_entity_type_id'
|
||||
default_langcode:
|
||||
type: langcode
|
||||
label: 'Default language'
|
||||
|
|
|
@ -106,6 +106,10 @@ class LanguageConfigurationElementTest extends BrowserTestBase {
|
|||
])->save();
|
||||
}
|
||||
|
||||
// Ensure the bundles under test exist, to avoid config validation errors.
|
||||
entity_test_create_bundle('custom_bundle');
|
||||
entity_test_create_bundle('some_bundle');
|
||||
|
||||
// Fixed language.
|
||||
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
|
||||
->setLanguageAlterable(TRUE)
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace Drupal\Tests\language\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests validation of content_language_settings entities.
|
||||
|
@ -13,10 +15,19 @@ use Drupal\language\Entity\ContentLanguageSettings;
|
|||
*/
|
||||
class ContentLanguageSettingsValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'user'];
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'field',
|
||||
'language',
|
||||
'node',
|
||||
'text',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -28,12 +39,40 @@ class ContentLanguageSettingsValidationTest extends ConfigEntityValidationTestBa
|
|||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installConfig('node');
|
||||
|
||||
$this->createContentType(['type' => 'alpha']);
|
||||
$this->createContentType(['type' => 'bravo']);
|
||||
|
||||
EntityTestBundle::create(['id' => 'alpha'])->save();
|
||||
EntityTestBundle::create(['id' => 'bravo'])->save();
|
||||
|
||||
$this->entity = ContentLanguageSettings::create([
|
||||
'target_entity_type_id' => 'user',
|
||||
'target_bundle' => 'user',
|
||||
'target_entity_type_id' => 'node',
|
||||
'target_bundle' => 'alpha',
|
||||
]);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the target bundle of the language content settings is checked.
|
||||
*/
|
||||
public function testTargetBundleMustExist(): void {
|
||||
$this->entity->set('target_bundle', 'superhero');
|
||||
$this->assertValidationErrors([
|
||||
'' => "The 'target_bundle' property cannot be changed.",
|
||||
'target_bundle' => "The 'superhero' bundle does not exist on the 'node' entity type.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testImmutableProperties(array $valid_values = []): void {
|
||||
parent::testImmutableProperties([
|
||||
'target_entity_type_id' => 'entity_test_with_bundle',
|
||||
'target_bundle' => 'bravo',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
namespace Drupal\Tests\layout_builder\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests validation of Layout Builder's entity_view_display entities.
|
||||
|
@ -14,10 +17,19 @@ use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
|||
*/
|
||||
class LayoutBuilderEntityViewDisplayValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['layout_builder', 'user'];
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'field',
|
||||
'layout_builder',
|
||||
'node',
|
||||
'text',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -25,18 +37,22 @@ class LayoutBuilderEntityViewDisplayValidationTest extends ConfigEntityValidatio
|
|||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig('node');
|
||||
$this->createContentType(['type' => 'one']);
|
||||
$this->createContentType(['type' => 'two']);
|
||||
|
||||
EntityTestBundle::create(['id' => 'one'])->save();
|
||||
EntityTestBundle::create(['id' => 'two'])->save();
|
||||
|
||||
EntityViewMode::create([
|
||||
'id' => 'user.layout',
|
||||
'id' => 'node.layout',
|
||||
'label' => 'Layout',
|
||||
'targetEntityType' => 'user',
|
||||
'targetEntityType' => 'node',
|
||||
])->save();
|
||||
|
||||
$this->entity = LayoutBuilderEntityViewDisplay::create([
|
||||
'mode' => 'layout',
|
||||
'label' => 'Layout',
|
||||
'targetEntityType' => 'user',
|
||||
'bundle' => 'user',
|
||||
]);
|
||||
$this->entity = $this->container->get(EntityDisplayRepositoryInterface::class)
|
||||
->getViewDisplay('node', 'one', 'layout');
|
||||
$this->assertInstanceOf(LayoutBuilderEntityViewDisplay::class, $this->entity);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
|
@ -49,4 +65,14 @@ class LayoutBuilderEntityViewDisplayValidationTest extends ConfigEntityValidatio
|
|||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testImmutableProperties(array $valid_values = []): void {
|
||||
parent::testImmutableProperties([
|
||||
'targetEntityType' => 'entity_test_with_bundle',
|
||||
'bundle' => 'two',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -83,6 +83,9 @@ class MediaLibraryDisplayModeTest extends BrowserTestBase {
|
|||
|
||||
// Display modes are created on install.
|
||||
$this->container->get('module_installer')->install(['media_library']);
|
||||
// The container was rebuilt during module installation, so ensure we have
|
||||
// an up-to-date reference to it.
|
||||
$this->container = $this->kernel->getContainer();
|
||||
|
||||
// For a non-image media type without a mapped name field, the media_library
|
||||
// form mode should only contain the name field.
|
||||
|
@ -183,6 +186,10 @@ class MediaLibraryDisplayModeTest extends BrowserTestBase {
|
|||
$this->assertFormDisplay($type_id, FALSE, FALSE);
|
||||
$this->assertViewDisplay($type_id, 'medium');
|
||||
|
||||
// Now that all our media types have been created, ensure the bundle info
|
||||
// cache is up-to-date.
|
||||
$this->container->get('entity_type.bundle.info')->clearCachedBundles();
|
||||
|
||||
// Delete a form and view display.
|
||||
EntityFormDisplay::load('media.type_one.media_library')->delete();
|
||||
EntityViewDisplay::load('media.type_one.media_library')->delete();
|
||||
|
|
|
@ -4,8 +4,8 @@ namespace Drupal\Tests\media_library\Kernel;
|
|||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\AccessResultReasonInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\entity_test\Entity\EntityTestWithBundle;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
@ -50,7 +50,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('file');
|
||||
$this->installSchema('file', 'file_usage');
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('entity_test_with_bundle');
|
||||
$this->installEntitySchema('filter_format');
|
||||
$this->installEntitySchema('media');
|
||||
$this->installConfig([
|
||||
|
@ -67,7 +67,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
$field_storage = FieldStorageConfig::create([
|
||||
'type' => 'entity_reference',
|
||||
'field_name' => 'field_test_media',
|
||||
'entity_type' => 'entity_test',
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'settings' => [
|
||||
'target_type' => 'media',
|
||||
],
|
||||
|
@ -92,7 +92,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
|
||||
// Create a media library state to test access.
|
||||
$state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [
|
||||
'entity_type_id' => 'entity_test',
|
||||
'entity_type_id' => 'entity_test_with_bundle',
|
||||
'bundle' => 'test',
|
||||
'field_name' => 'field_test_media',
|
||||
]);
|
||||
|
@ -187,7 +187,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
/** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */
|
||||
$ui_builder = $this->container->get('media_library.ui_builder');
|
||||
|
||||
$forbidden_entity = EntityTest::create([
|
||||
$forbidden_entity = EntityTestWithBundle::create([
|
||||
'type' => 'test',
|
||||
// This label will automatically cause an access denial.
|
||||
// @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
|
||||
|
@ -206,7 +206,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
$access_result = $ui_builder->checkAccess($this->createUser(), $state);
|
||||
$this->assertAccess($access_result, FALSE, NULL, [], ['url.query_args']);
|
||||
|
||||
$neutral_entity = EntityTest::create([
|
||||
$neutral_entity = EntityTestWithBundle::create([
|
||||
'type' => 'test',
|
||||
// This label will result in neutral access.
|
||||
// @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
|
||||
|
@ -262,7 +262,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
public function testFieldWidgetEntityFieldAccess(string $field_type) {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'type' => $field_type,
|
||||
'entity_type' => 'entity_test',
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
// The media_library_test module will deny access to this field.
|
||||
// @see media_library_test_entity_field_access()
|
||||
'field_name' => 'field_media_no_access',
|
||||
|
@ -286,7 +286,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
|
||||
// Test that access is denied even without an entity to work with.
|
||||
$state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [
|
||||
'entity_type_id' => 'entity_test',
|
||||
'entity_type_id' => 'entity_test_with_bundle',
|
||||
'bundle' => 'test',
|
||||
'field_name' => $field_storage->getName(),
|
||||
]);
|
||||
|
@ -294,7 +294,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
$this->assertAccess($access_result, FALSE, 'Field access denied by test module', [], ['url.query_args', 'user.permissions']);
|
||||
|
||||
// Assert that field access is also checked with a real entity.
|
||||
$entity = EntityTest::create([
|
||||
$entity = EntityTestWithBundle::create([
|
||||
'type' => 'test',
|
||||
'name' => $this->randomString(),
|
||||
]);
|
||||
|
@ -323,7 +323,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
|
||||
// Create a media library state to test access.
|
||||
$state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [
|
||||
'entity_type_id' => 'entity_test',
|
||||
'entity_type_id' => 'entity_test_with_bundle',
|
||||
'bundle' => 'test',
|
||||
'field_name' => 'field_test_media',
|
||||
]);
|
||||
|
|
|
@ -141,7 +141,13 @@ class EntityAddUITest extends BrowserTestBase {
|
|||
$this->drupalLogin($admin_user);
|
||||
|
||||
entity_test_create_bundle('test', 'Test label', 'entity_test_mul');
|
||||
// Delete the default bundle, so that we can rely on our own.
|
||||
// Delete the default bundle, so that we can rely on our own. The form
|
||||
// display has to be deleted first to prevent schema errors when fields
|
||||
// attached to the deleted bundle are themselves deleted, which triggers
|
||||
// an update of the form display.
|
||||
$this->container->get('entity_display.repository')
|
||||
->getFormDisplay('entity_test_mul', 'entity_test_mul')
|
||||
->delete();
|
||||
entity_test_delete_bundle('entity_test_mul', 'entity_test_mul');
|
||||
|
||||
// One bundle exists, confirm redirection to the add-form.
|
||||
|
|
|
@ -82,6 +82,8 @@ class TermEntityReferenceTest extends KernelTestBase {
|
|||
'cardinality' => 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
entity_test_create_bundle('test_bundle');
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'entity_type' => 'entity_test',
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
namespace Drupal\KernelTests\Core\Entity;
|
||||
|
||||
use Drupal\Core\Field\Entity\BaseFieldOverride;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests validation of base_field_override entities.
|
||||
|
@ -13,10 +15,12 @@ use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
|||
*/
|
||||
class BaseFieldOverrideValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user'];
|
||||
protected static $modules = ['entity_test', 'field', 'node', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -24,13 +28,31 @@ class BaseFieldOverrideValidationTest extends ConfigEntityValidationTestBase {
|
|||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$fields = $this->container->get('entity_field.manager')
|
||||
->getBaseFieldDefinitions('user');
|
||||
$this->installConfig('node');
|
||||
$this->createContentType(['type' => 'one']);
|
||||
$this->createContentType(['type' => 'another']);
|
||||
|
||||
$this->entity = BaseFieldOverride::createFromBaseFieldDefinition(reset($fields), 'user');
|
||||
EntityTestBundle::create(['id' => 'one'])->save();
|
||||
EntityTestBundle::create(['id' => 'another'])->save();
|
||||
|
||||
$fields = $this->container->get('entity_field.manager')
|
||||
->getBaseFieldDefinitions('node');
|
||||
|
||||
$this->entity = BaseFieldOverride::createFromBaseFieldDefinition(reset($fields), 'one');
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the target bundle of the field is checked.
|
||||
*/
|
||||
public function testTargetBundleMustExist(): void {
|
||||
$this->entity->set('bundle', 'nope');
|
||||
$this->assertValidationErrors([
|
||||
'' => "The 'bundle' property cannot be changed.",
|
||||
'bundle' => "The 'nope' bundle does not exist on the 'node' entity type.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -40,7 +62,10 @@ class BaseFieldOverrideValidationTest extends ConfigEntityValidationTestBase {
|
|||
// settings from the *old* field_type won't match the config schema for the
|
||||
// settings of the *new* field_type.
|
||||
$this->entity->set('settings', []);
|
||||
parent::testImmutableProperties($valid_values);
|
||||
parent::testImmutableProperties([
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'bundle' => 'another',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Entity;
|
||||
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\Core\TypedData\MapDataDefinition;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* @group Entity
|
||||
* @group Validation
|
||||
*
|
||||
* @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\EntityBundleExistsConstraint
|
||||
* @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\EntityBundleExistsConstraintValidator
|
||||
*/
|
||||
class EntityBundleExistsConstraintValidatorTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
EntityTestBundle::create([
|
||||
'id' => 'foo',
|
||||
'label' => 'Test',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the constraint validator will only work with strings.
|
||||
*/
|
||||
public function testValueMustBeAString(): void {
|
||||
$definition = DataDefinition::create('any')
|
||||
->addConstraint('EntityBundleExists', 'entity_test_with_bundle');
|
||||
|
||||
$this->expectException(UnexpectedTypeException::class);
|
||||
$this->expectExceptionMessage('Expected argument of type "string", "int" given');
|
||||
$this->container->get('typed_data_manager')
|
||||
->create($definition, 39)
|
||||
->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validating a bundle of a known (static) entity type ID.
|
||||
*/
|
||||
public function testEntityTypeIdIsStatic(): void {
|
||||
$definition = DataDefinition::create('string')
|
||||
->addConstraint('EntityBundleExists', 'entity_test_with_bundle');
|
||||
|
||||
$violations = $this->container->get('typed_data_manager')
|
||||
->create($definition, 'bar')
|
||||
->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame("The 'bar' bundle does not exist on the 'entity_test_with_bundle' entity type.", (string) $violations->get(0)->getMessage());
|
||||
$this->assertSame('', $violations->get(0)->getPropertyPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting the entity type ID from the parent property path.
|
||||
*
|
||||
* @param string $constraint_value
|
||||
* The entity type ID to supply to the validation constraint. Must be a
|
||||
* dynamic token starting with %.
|
||||
* @param string $resolved_entity_type_id
|
||||
* The actual entity type ID which should be checked for the existence of
|
||||
* a bundle.
|
||||
*
|
||||
* @testWith ["%parent.entity_type_id", "entity_test_with_bundle"]
|
||||
* ["%paren.entity_type_id", "%paren.entity_type_id"]
|
||||
*/
|
||||
public function testEntityTypeIdFromParent(string $constraint_value, string $resolved_entity_type_id): void {
|
||||
/** @var \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager */
|
||||
$typed_data_manager = $this->container->get('typed_data_manager');
|
||||
|
||||
$this->assertStringStartsWith('%', $constraint_value);
|
||||
$value_definition = DataDefinition::create('string')
|
||||
->addConstraint('EntityBundleExists', $constraint_value);
|
||||
|
||||
$parent_definition = MapDataDefinition::create()
|
||||
->setPropertyDefinition('entity_type_id', DataDefinition::create('string'))
|
||||
->setPropertyDefinition('bundle', $value_definition);
|
||||
|
||||
$violations = $typed_data_manager->create($parent_definition, [
|
||||
'entity_type_id' => 'entity_test_with_bundle',
|
||||
'bundle' => 'bar',
|
||||
])->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame("The 'bar' bundle does not exist on the '$resolved_entity_type_id' entity type.", (string) $violations->get(0)->getMessage());
|
||||
$this->assertSame('bundle', $violations->get(0)->getPropertyPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting the entity type ID from a deeply nested property path.
|
||||
*/
|
||||
public function testEntityTypeIdFromMultipleParents(): void {
|
||||
$tree_definition = MapDataDefinition::create()
|
||||
->setPropertyDefinition('info', MapDataDefinition::create()
|
||||
->setPropertyDefinition('entity_type_id', DataDefinition::create('string'))
|
||||
)
|
||||
->setPropertyDefinition('info2', MapDataDefinition::create()
|
||||
->setPropertyDefinition('bundle', DataDefinition::create('string')
|
||||
->addConstraint('EntityBundleExists', '%parent.%parent.info.entity_type_id')
|
||||
)
|
||||
);
|
||||
|
||||
$violations = $this->container->get('typed_data_manager')
|
||||
->create($tree_definition, [
|
||||
'info' => [
|
||||
'entity_type_id' => 'entity_test_with_bundle',
|
||||
],
|
||||
'info2' => [
|
||||
'bundle' => 'bar',
|
||||
],
|
||||
])
|
||||
->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame("The 'bar' bundle does not exist on the 'entity_test_with_bundle' entity type.", (string) $violations->get(0)->getMessage());
|
||||
$this->assertSame('info2.bundle', $violations->get(0)->getPropertyPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests when the constraint's entityTypeId value is not valid.
|
||||
*/
|
||||
public function testInvalidEntityTypeId(): void {
|
||||
$entity_type_id = $this->randomMachineName();
|
||||
$definition = DataDefinition::create('string')
|
||||
->addConstraint('EntityBundleExists', $entity_type_id);
|
||||
|
||||
$violations = $this->container->get('typed_data_manager')
|
||||
->create($definition, 'bar')
|
||||
->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame("The 'bar' bundle does not exist on the '$entity_type_id' entity type.", (string) $violations->get(0)->getMessage());
|
||||
$this->assertSame('', $violations->get(0)->getPropertyPath());
|
||||
}
|
||||
|
||||
}
|
|
@ -3,9 +3,13 @@
|
|||
namespace Drupal\KernelTests\Core\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityFormMode;
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests validation of entity_form_display entities.
|
||||
|
@ -13,25 +17,41 @@ use Drupal\field\Entity\FieldStorageConfig;
|
|||
* @group Entity
|
||||
* @group Validation
|
||||
*/
|
||||
class EntityFormDisplayValidationTest extends EntityFormModeValidationTest {
|
||||
class EntityFormDisplayValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected bool $hasLabel = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'field', 'node', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entity = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'user',
|
||||
'bundle' => 'user',
|
||||
// The mode was created by the parent class.
|
||||
'mode' => 'test',
|
||||
]);
|
||||
$this->installConfig('node');
|
||||
$this->createContentType(['type' => 'one']);
|
||||
$this->createContentType(['type' => 'two']);
|
||||
|
||||
EntityTestBundle::create(['id' => 'one'])->save();
|
||||
EntityTestBundle::create(['id' => 'two'])->save();
|
||||
|
||||
EntityFormMode::create([
|
||||
'id' => 'node.test',
|
||||
'label' => 'Test',
|
||||
'targetEntityType' => 'node',
|
||||
])->save();
|
||||
|
||||
$this->entity = $this->container->get(EntityDisplayRepositoryInterface::class)
|
||||
->getFormDisplay('node', 'one', 'test');
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
|
@ -40,7 +60,6 @@ class EntityFormDisplayValidationTest extends EntityFormModeValidationTest {
|
|||
*/
|
||||
public function testMultilineTextFieldWidgetPlaceholder(): void {
|
||||
// First, create a field for which widget settings exist.
|
||||
$this->enableModules(['field', 'text']);
|
||||
$text_field_storage_config = FieldStorageConfig::create([
|
||||
'type' => 'text_with_summary',
|
||||
'field_name' => 'novel',
|
||||
|
@ -76,4 +95,25 @@ class EntityFormDisplayValidationTest extends EntityFormModeValidationTest {
|
|||
$this->assertValidationErrors([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the target bundle of the entity form display is checked.
|
||||
*/
|
||||
public function testTargetBundleMustExist(): void {
|
||||
$this->entity->set('bundle', 'superhero');
|
||||
$this->assertValidationErrors([
|
||||
'' => "The 'bundle' property cannot be changed.",
|
||||
'bundle' => "The 'superhero' bundle does not exist on the 'node' entity type.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testImmutableProperties(array $valid_values = []): void {
|
||||
parent::testImmutableProperties([
|
||||
'targetEntityType' => 'entity_test_with_bundle',
|
||||
'bundle' => 'two',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ class EntityQueryTest extends EntityKernelTestBase {
|
|||
do {
|
||||
$bundle = $this->randomMachineName();
|
||||
} while ($bundles && strtolower($bundles[0]) >= strtolower($bundle));
|
||||
entity_test_create_bundle($bundle);
|
||||
entity_test_create_bundle($bundle, entity_type: $field_storage->getTargetEntityTypeId());
|
||||
foreach ($field_storages as $field_storage) {
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
|
@ -562,6 +562,7 @@ class EntityQueryTest extends EntityKernelTestBase {
|
|||
]);
|
||||
$field_storage->save();
|
||||
$bundle = $this->randomMachineName();
|
||||
entity_test_create_bundle($bundle);
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $bundle,
|
||||
|
@ -813,6 +814,7 @@ class EntityQueryTest extends EntityKernelTestBase {
|
|||
*/
|
||||
public function testCaseSensitivity() {
|
||||
$bundle = $this->randomMachineName();
|
||||
entity_test_create_bundle($bundle, entity_type: 'entity_test_mulrev');
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_ci',
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
namespace Drupal\KernelTests\Core\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests validation of entity_view_display entities.
|
||||
|
@ -10,26 +14,63 @@ use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
|||
* @group Entity
|
||||
* @group Validation
|
||||
*/
|
||||
class EntityViewDisplayValidationTest extends EntityViewModeValidationTest {
|
||||
class EntityViewDisplayValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected bool $hasLabel = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'field', 'node', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entity = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'user',
|
||||
'bundle' => 'user',
|
||||
// The mode was created by the parent class.
|
||||
'mode' => 'test',
|
||||
]);
|
||||
$this->installConfig('node');
|
||||
$this->createContentType(['type' => 'one']);
|
||||
$this->createContentType(['type' => 'two']);
|
||||
|
||||
EntityTestBundle::create(['id' => 'one'])->save();
|
||||
EntityTestBundle::create(['id' => 'two'])->save();
|
||||
|
||||
EntityViewMode::create([
|
||||
'id' => 'node.test',
|
||||
'label' => 'Test',
|
||||
'targetEntityType' => 'node',
|
||||
])->save();
|
||||
|
||||
$this->entity = $this->container->get(EntityDisplayRepositoryInterface::class)
|
||||
->getViewDisplay('node', 'one', 'test');
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the target bundle of the entity view display is checked.
|
||||
*/
|
||||
public function testTargetBundleMustExist(): void {
|
||||
$this->entity->set('bundle', 'superhero');
|
||||
$this->assertValidationErrors([
|
||||
'' => "The 'bundle' property cannot be changed.",
|
||||
'bundle' => "The 'superhero' bundle does not exist on the 'node' entity type.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testImmutableProperties(array $valid_values = []): void {
|
||||
parent::testImmutableProperties([
|
||||
'targetEntityType' => 'entity_test_with_bundle',
|
||||
'bundle' => 'two',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue