Verified Commit 2c3cb5e8 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3427564 by phenaproxima, Wim Leers, alexpott, omkar.podey, Berdir,...

Issue #3427564 by phenaproxima, Wim Leers, alexpott, omkar.podey, Berdir, smustgrave, larowlan: Require `langcode: …` only for simple config that contains translatable values

(cherry picked from commit 83a53ba4)
parent 6b2ecdbb
Loading
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -207,7 +207,15 @@ config_object:
      requiredKey: false
      type: _core_config_info
    langcode:
      requiredKey: false
      type: langcode
  constraints:
    # The `langcode` key:
    # - MUST be specified when there are translatable values
    # - MUST NOT be specified when there are no translatable values.
    # Translatable values are specified for this config schema type (a subtype of `type: config_object`) if the
    # `translatable` flag is present and set to `true` for *any* element in that config schema type.
    LangcodeRequiredIfTranslatableValues: ~

# Mail text with subject and body parts.
mail:
+32 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Config\Plugin\Validation\Constraint;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Validation\Attribute\Constraint;
use Symfony\Component\Validator\Constraint as SymfonyConstraint;

#[Constraint(
  id: 'LangcodeRequiredIfTranslatableValues',
  label: new TranslatableMarkup('Translatable config has langcode', [], ['context' => 'Validation']),
  type: ['config_object']
)]
class LangcodeRequiredIfTranslatableValuesConstraint extends SymfonyConstraint {

  /**
   * The error message if this config object is missing a `langcode`.
   *
   * @var string
   */
  public string $missingMessage = "The @name config object must specify a language code, because it contains translatable values.";

  /**
   * The error message if this config object contains a superfluous `langcode`.
   *
   * @var string
   */
  public string $superfluousMessage = "The @name config object does not contain any translatable values, so it should not specify a language code.";

}
+47 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Config\Plugin\Validation\Constraint;

use Drupal\Core\Config\Schema\Mapping;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\LogicException;

/**
 * Validates the LangcodeRequiredIfTranslatableValues constraint.
 */
final class LangcodeRequiredIfTranslatableValuesConstraintValidator extends ConstraintValidator {

  /**
   * {@inheritdoc}
   */
  public function validate(mixed $value, Constraint $constraint) {
    assert($constraint instanceof LangcodeRequiredIfTranslatableValuesConstraint);

    $mapping = $this->context->getObject();
    assert($mapping instanceof Mapping);
    if ($mapping !== $this->context->getRoot()) {
      throw new LogicException('The LangcodeRequiredIfTranslatableValues constraint can only operate on the root object being validated.');
    }

    assert(in_array('langcode', $mapping->getValidKeys(), TRUE));

    $is_translatable = $mapping->hasTranslatableElements();

    if ($is_translatable && !array_key_exists('langcode', $value)) {
      $this->context->buildViolation($constraint->missingMessage)
        ->setParameter('@name', $mapping->getName())
        ->addViolation();
      return;
    }
    if (!$is_translatable && array_key_exists('langcode', $value)) {
      // @todo Convert this deprecation to an actual validation error in
      //   https://www.drupal.org/project/drupal/issues/3440238.
      // phpcs:ignore
      @trigger_error(str_replace('@name', $mapping->getName(), $constraint->superfluousMessage), E_USER_DEPRECATED);
    }
  }

}
+19 −0
Original line number Diff line number Diff line
@@ -14,6 +14,25 @@ abstract class ArrayElement extends Element implements \IteratorAggregate, Typed
   */
  protected $elements;

  /**
   * Determines if there is a translatable value.
   *
   * @return bool
   *   Returns true if a translatable element is found.
   */
  public function hasTranslatableElements(): bool {
    foreach ($this as $element) {
      // Early return if found.
      if ($element->getDataDefinition()['translatable'] === TRUE) {
        return TRUE;
      }
      if ($element instanceof ArrayElement && $element->hasTranslatableElements()) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Gets valid configuration data keys.
   *
+0 −7
Original line number Diff line number Diff line
@@ -93,13 +93,6 @@ trait SchemaCheckTrait {
   *   valid.
   */
  public function checkConfigSchema(TypedConfigManagerInterface $typed_config, $config_name, $config_data, bool $validate_constraints = FALSE) {
    // We'd like to verify that the top-level type is either config_base,
    // config_entity, or a derivative. The only thing we can really test though
    // is that the schema supports having langcode in it. So add 'langcode' to
    // the data if it doesn't already exist.
    if (!isset($config_data['langcode'])) {
      $config_data['langcode'] = 'en';
    }
    $this->configName = $config_name;
    if (!$typed_config->hasConfigSchema($config_name)) {
      return FALSE;
Loading