@amazeelabs/silverback-gutenberg 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +983 -0
  2. package/drupal/silverback_gutenberg/README.md +439 -0
  3. package/drupal/silverback_gutenberg/composer.json +20 -0
  4. package/drupal/silverback_gutenberg/config/install/silverback_gutenberg.settings.yml +1 -0
  5. package/drupal/silverback_gutenberg/config/schema/silverback_gutenberg.schema.yml +13 -0
  6. package/drupal/silverback_gutenberg/css/gutenberg-tweaks.css +46 -0
  7. package/drupal/silverback_gutenberg/directives.gql +40 -0
  8. package/drupal/silverback_gutenberg/directives.graphql +46 -0
  9. package/drupal/silverback_gutenberg/js/base.js +24 -0
  10. package/drupal/silverback_gutenberg/js/gutenberg-tweaks.js +154 -0
  11. package/drupal/silverback_gutenberg/silverback_gutenberg.api.php +76 -0
  12. package/drupal/silverback_gutenberg/silverback_gutenberg.info.yml +8 -0
  13. package/drupal/silverback_gutenberg/silverback_gutenberg.libraries.yml +14 -0
  14. package/drupal/silverback_gutenberg/silverback_gutenberg.module +97 -0
  15. package/drupal/silverback_gutenberg/silverback_gutenberg.services.yml +29 -0
  16. package/drupal/silverback_gutenberg/src/Annotation/GutenbergBlockMutator.php +39 -0
  17. package/drupal/silverback_gutenberg/src/Annotation/GutenbergValidator.php +37 -0
  18. package/drupal/silverback_gutenberg/src/Annotation/GutenbergValidatorRule.php +37 -0
  19. package/drupal/silverback_gutenberg/src/Attribute/GutenbergBlockMutator.php +29 -0
  20. package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorBase.php +24 -0
  21. package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorInterface.php +41 -0
  22. package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorManager.php +114 -0
  23. package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorManagerInterface.php +30 -0
  24. package/drupal/silverback_gutenberg/src/BlockMutator/EntityBlockMutatorBase.php +189 -0
  25. package/drupal/silverback_gutenberg/src/BlockSerializer.php +84 -0
  26. package/drupal/silverback_gutenberg/src/Controller/LinkitAutocomplete.php +84 -0
  27. package/drupal/silverback_gutenberg/src/Directives.php +74 -0
  28. package/drupal/silverback_gutenberg/src/EditorBlocksProcessor.php +53 -0
  29. package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergCardinalityValidatorInterface.php +19 -0
  30. package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergCardinalityValidatorTrait.php +221 -0
  31. package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorBase.php +24 -0
  32. package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorInterface.php +65 -0
  33. package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorManager.php +37 -0
  34. package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorRuleInterface.php +20 -0
  35. package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorRuleManager.php +37 -0
  36. package/drupal/silverback_gutenberg/src/LinkProcessor.php +405 -0
  37. package/drupal/silverback_gutenberg/src/LinkedContentExtractor.php +35 -0
  38. package/drupal/silverback_gutenberg/src/Normalizer/GutenbergContentEntityNormalizer.php +123 -0
  39. package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergContentTrackTrait.php +51 -0
  40. package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergLinkedContent.php +96 -0
  41. package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergMediaEmbed.php +63 -0
  42. package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergReferencedContent.php +101 -0
  43. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockAttribute.php +42 -0
  44. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockChildren.php +32 -0
  45. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockHtml.php +30 -0
  46. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockMedia.php +159 -0
  47. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockType.php +29 -0
  48. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlocks.php +127 -0
  49. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockAttribute.php +29 -0
  50. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockChildren.php +21 -0
  51. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockMarkup.php +21 -0
  52. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockMedia.php +21 -0
  53. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockType.php +21 -0
  54. package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlocks.php +36 -0
  55. package/drupal/silverback_gutenberg/src/Plugin/GutenbergBlockMutator/MediaBlockMutator.php +30 -0
  56. package/drupal/silverback_gutenberg/src/Plugin/GutenbergBlockMutator/NodeBlockMutator.php +25 -0
  57. package/drupal/silverback_gutenberg/src/Plugin/GutenbergBlockMutator/TermReferenceBlockMutator.php +104 -0
  58. package/drupal/silverback_gutenberg/src/Plugin/Linkit/Matcher/SilverbackMatcherTrait.php +69 -0
  59. package/drupal/silverback_gutenberg/src/Plugin/Linkit/Matcher/SilverbackMediaMatcher.php +53 -0
  60. package/drupal/silverback_gutenberg/src/Plugin/Linkit/Matcher/SilverbackNodeMatcher.php +19 -0
  61. package/drupal/silverback_gutenberg/src/Plugin/Validation/Constraint/Gutenberg.php +15 -0
  62. package/drupal/silverback_gutenberg/src/Plugin/Validation/Constraint/GutenbergValidator.php +210 -0
  63. package/drupal/silverback_gutenberg/src/Plugin/Validation/GutenbergValidatorRule/Email.php +28 -0
  64. package/drupal/silverback_gutenberg/src/Plugin/Validation/GutenbergValidatorRule/Required.php +29 -0
  65. package/drupal/silverback_gutenberg/src/ReferencedContentExtractor.php +67 -0
  66. package/drupal/silverback_gutenberg/src/Routing/RouteSubscriber.php +17 -0
  67. package/drupal/silverback_gutenberg/src/Service/MediaService.php +27 -0
  68. package/drupal/silverback_gutenberg/src/SilverbackGutenbergServiceProvider.php +28 -0
  69. package/drupal/silverback_gutenberg/src/Utils.php +30 -0
  70. package/drupal/silverback_gutenberg/src/WebformMessageManager.php +29 -0
  71. package/drupal/silverback_gutenberg/tests/graphql/.graphqlrc.json +5 -0
  72. package/drupal/silverback_gutenberg/tests/graphql/queries/editor.gql +30 -0
  73. package/drupal/silverback_gutenberg/tests/graphql/schema.graphqls +37 -0
  74. package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/silverback_gutenberg_test_validator.info.yml +9 -0
  75. package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/src/Plugin/Validation/GutenbergValidator/ColumnValidator.php +42 -0
  76. package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/src/Plugin/Validation/GutenbergValidator/GroupValidator.php +50 -0
  77. package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/src/Plugin/Validation/GutenbergValidator/LinkValidator.php +43 -0
  78. package/drupal/silverback_gutenberg/tests/src/Kernel/BlockValidationRuleTest.php +194 -0
  79. package/drupal/silverback_gutenberg/tests/src/Kernel/EditorDirectivesTest.php +255 -0
  80. package/drupal/silverback_gutenberg/tests/src/Kernel/GutenbergLinkedContentEUTrackTest.php +133 -0
  81. package/drupal/silverback_gutenberg/tests/src/Kernel/GutenbergReferencedContentEUTrackTest.php +225 -0
  82. package/drupal/silverback_gutenberg/tests/src/Kernel/LinkProcessorTest.php +284 -0
  83. package/drupal/silverback_gutenberg/tests/src/Kernel/MediaNormalizerTest.php +174 -0
  84. package/drupal/silverback_gutenberg/tests/src/Traits/SampleAssetTrait.php +15 -0
  85. package/drupal/silverback_gutenberg/tests/src/Unit/BlockSerializerTest.php +27 -0
  86. package/drupal/silverback_gutenberg/tests/src/Unit/BlockValidatorCardinalityTest.php +1537 -0
  87. package/drupal/silverback_gutenberg/tests/src/Unit/EditorBlocksProcessorTest.php +159 -0
  88. package/drupal/silverback_gutenberg/tests/src/Unit/LinkedContentExtractorTest.php +65 -0
  89. package/drupal/silverback_gutenberg/tests/src/Unit/ReferencedContentExtractorTest.php +248 -0
  90. package/drupal/silverback_gutenberg/tests/src/assets/media/data.json +4 -0
  91. package/drupal/silverback_gutenberg/tests/src/assets/media/source.html +71 -0
  92. package/drupal/silverback_gutenberg/tests/src/assets/media/target.html +71 -0
  93. package/package.json +16 -0
  94. package/turbo.json +15 -0
@@ -0,0 +1,28 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\Plugin\Validation\GutenbergValidatorRule;
4
+
5
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface;
6
+ use Drupal\Core\StringTranslation\StringTranslationTrait;
7
+
8
+ /**
9
+ * @GutenbergValidatorRule(
10
+ * id="email",
11
+ * label = @Translation("Email")
12
+ * )
13
+ */
14
+ class Email implements GutenbergValidatorRuleInterface {
15
+
16
+ use StringTranslationTrait;
17
+
18
+ /**
19
+ * {@inheritDoc}
20
+ */
21
+ public function validate($value, $fieldLabel): bool|string {
22
+ if (!empty($value) && !\Drupal::service('email.validator')->isValid($value)) {
23
+ return $this->t('%field is not valid.', ['%field' => $fieldLabel]);
24
+ }
25
+ return TRUE;
26
+ }
27
+
28
+ }
@@ -0,0 +1,29 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\Plugin\Validation\GutenbergValidatorRule;
4
+
5
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface;
6
+ use Drupal\Core\StringTranslation\StringTranslationTrait;
7
+
8
+ /**
9
+ * @GutenbergValidatorRule(
10
+ * id="required",
11
+ * label = @Translation("Required")
12
+ * )
13
+ */
14
+ class Required implements GutenbergValidatorRuleInterface {
15
+
16
+ use StringTranslationTrait;
17
+ public $requiredMessage = '%field field is required.';
18
+
19
+ /**
20
+ * {@inheritDoc}
21
+ */
22
+ public function validate($value, $fieldLabel): bool|string {
23
+ if (empty($value) || $value === '_none') {
24
+ return $this->t($this->requiredMessage, ['%field' => $fieldLabel]);
25
+ }
26
+ return TRUE;
27
+ }
28
+
29
+ }
@@ -0,0 +1,67 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg;
4
+
5
+ class ReferencedContentExtractor {
6
+
7
+ /**
8
+ * Given an array of Gutenberg blocks, it returns all the referenced content
9
+ * based on specific entity type and uuid attributes.
10
+ *
11
+ * @param array $gutenbergBlocks
12
+ * An array of parsed gutenberg blocks.
13
+ * @param string $entityTypeAttribute
14
+ * The attribute name which identifies the entity type.
15
+ * @param string $uuidAttribute
16
+ * The attribute name which identifies the uuid.
17
+ * @return array
18
+ */
19
+ public function getTargetEntities(
20
+ array $gutenbergBlocks,
21
+ string $entityTypeAttribute = 'entityType',
22
+ string $uuidAttribute = 'uuid'
23
+ ) {
24
+ $targetEntities = [];
25
+ $this->extractReferencesFromGutenbergBlocks($gutenbergBlocks, $entityTypeAttribute, $uuidAttribute, $targetEntities);
26
+ return $targetEntities;
27
+ }
28
+
29
+ /**
30
+ * Recursively extracts the content that is referenced inside an array of
31
+ * Gutenberg blocks. This is basically a helper for the public
32
+ * getTargetEntities() method.
33
+ *
34
+ * @param array $blocks
35
+ * An array of parsed gutenberg blocks.
36
+ * @param string $entityTypeAttribute
37
+ * The attribute name which identifies the entity type.
38
+ * @param string $uuidAttribute
39
+ * The attribute name which identifies the uuid.
40
+ * @param array $references
41
+ * An array with all the extracted references until the current call.
42
+ * @return void
43
+ */
44
+ protected function extractReferencesFromGutenbergBlocks(
45
+ array $blocks,
46
+ string $entityTypeAttribute,
47
+ string $uuidAttribute,
48
+ array &$references
49
+ ) {
50
+ foreach ($blocks as $block) {
51
+ if (!empty($block['attrs'][$uuidAttribute])) {
52
+ $uuid = $block['attrs'][$uuidAttribute];
53
+ // The default entity type should be, for convenience, node.
54
+ $entityType = $block['attrs'][$entityTypeAttribute] ?? 'node';
55
+ // Usually, the uuid would be one single value, but we also support an
56
+ // array of values.
57
+ $uuidList = is_array($uuid) ? $uuid : [$uuid];
58
+ foreach ($uuidList as $uuidItem) {
59
+ $references[$entityType][$uuidItem] = $uuidItem;
60
+ }
61
+ }
62
+ if (!empty($block['innerBlocks'])) {
63
+ $this->extractReferencesFromGutenbergBlocks($block['innerBlocks'], $entityTypeAttribute, $uuidAttribute, $references);
64
+ }
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,17 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\Routing;
4
+
5
+ use Drupal\Core\Routing\RouteSubscriberBase;
6
+ use Drupal\silverback_gutenberg\Controller\LinkitAutocomplete;
7
+ use Symfony\Component\Routing\RouteCollection;
8
+
9
+ class RouteSubscriber extends RouteSubscriberBase {
10
+
11
+ protected function alterRoutes(RouteCollection $collection) {
12
+ if ($route = $collection->get('gutenberg.content.search')) {
13
+ $route->setDefault('_controller', LinkitAutocomplete::class . '::search');
14
+ }
15
+ }
16
+
17
+ }
@@ -0,0 +1,27 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\Service;
4
+
5
+ use Drupal\gutenberg\Service\MediaService as Original;
6
+ use Drupal\media_library\MediaLibraryState;
7
+
8
+ class MediaService extends Original {
9
+
10
+ public function renderDialog(array $media_types, array $media_bundles = NULL) {
11
+ // Instead of guessing media types as the parent method does, use given
12
+ // media types as media type IDs. So the blocks have full control over
13
+ // allowed media types.
14
+ // The default behavior is: get bundles by $media_types, then intersect them
15
+ // with $media_bundles.
16
+ // New behavior is: get bundles from $media_types, so we don't break
17
+ // projects using this module.
18
+ $media_bundles = $media_types;
19
+
20
+ $buildUi = $this->builder->buildUi(
21
+ MediaLibraryState::create('gutenberg.media_library.opener', array_unique($media_bundles), reset($media_bundles), 1)
22
+ );
23
+ $this->moduleHandler->alter('gutenberg_media_library_view', $buildUi);
24
+ return $this->renderer->render($buildUi);
25
+ }
26
+
27
+ }
@@ -0,0 +1,28 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg;
4
+
5
+ use Drupal\Core\DependencyInjection\ContainerBuilder;
6
+ use Drupal\Core\DependencyInjection\ServiceProviderBase;
7
+ use Drupal\silverback_gutenberg\Normalizer\GutenbergContentEntityNormalizer;
8
+ use Drupal\silverback_gutenberg\Service\MediaService;
9
+ use Symfony\Component\DependencyInjection\Reference;
10
+
11
+ class SilverbackGutenbergServiceProvider extends ServiceProviderBase {
12
+ public function alter(ContainerBuilder $container) {
13
+ if ($container->has('gutenberg.media_service')) {
14
+ $definition = $container->getDefinition('gutenberg.media_service');
15
+ $definition->setClass(MediaService::class);
16
+ }
17
+ if ($container->has('webform.message_manager')) {
18
+ $definition = $container->getDefinition('webform.message_manager');
19
+ $definition->setClass(WebformMessageManager::class);
20
+ }
21
+
22
+ if ($container->has('default_content.content_entity_normalizer')) {
23
+ $definition = $container->getDefinition('default_content.content_entity_normalizer');
24
+ $definition->setClass(GutenbergContentEntityNormalizer::class);
25
+ $definition->addArgument(new Reference('plugin.manager.block_mutator'));
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,30 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg;
4
+
5
+ use Drupal\Core\Entity\EntityInterface;
6
+ use Drupal\Core\Entity\FieldableEntityInterface;
7
+ use Drupal\gutenberg\Controller\UtilsController;
8
+
9
+ class Utils {
10
+
11
+ public static function getGutenbergFields(EntityInterface $entity): array {
12
+ if (!function_exists('_gutenberg_is_gutenberg_enabled') || !_gutenberg_is_gutenberg_enabled($entity)) {
13
+ return [];
14
+ }
15
+ if ($entity instanceof FieldableEntityInterface) {
16
+ $textFields = UtilsController::getEntityTextFields($entity);
17
+ if (empty($textFields)) {
18
+ return [];
19
+ }
20
+ // At the moment Gutenberg uses the first text field.
21
+ return [$textFields[0]];
22
+ }
23
+ return [];
24
+ }
25
+
26
+ public static function linkProcessor(): LinkProcessor {
27
+ return \Drupal::service(LinkProcessor::class);
28
+ }
29
+
30
+ }
@@ -0,0 +1,29 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg;
4
+
5
+ use Drupal\webform\WebformMessageManager as Original;
6
+
7
+ class WebformMessageManager extends Original {
8
+
9
+ public function build($key) {
10
+ $build = parent::build($key);
11
+
12
+ if (isset($build['#markup'])) {
13
+ /** @var \Drupal\silverback_gutenberg\LinkProcessor $linkProcessor */
14
+ $linkProcessor = \Drupal::service(LinkProcessor::class);
15
+
16
+ // Ensure that the redirect links point to correct URLs.
17
+ $build['#markup'] = $linkProcessor->processLinks($build['#markup'], 'inbound');
18
+ $build['#markup'] = $linkProcessor->processLinks(
19
+ $build['#markup'],
20
+ 'outbound',
21
+ \Drupal::languageManager()->getCurrentLanguage()
22
+ );
23
+ $build['#cache']['max-age'] = 0;
24
+ }
25
+
26
+ return $build;
27
+ }
28
+
29
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "GraphQL Directives - Gutenberg",
3
+ "schemaPath": "./schema.graphqls",
4
+ "includes": ["./directives.graphqls"]
5
+ }
@@ -0,0 +1,30 @@
1
+ fragment Page on Page {
2
+ title
3
+ content {
4
+ __typename
5
+ ... on Text {
6
+ content
7
+ }
8
+ ... on Figure {
9
+ caption
10
+ image {
11
+ alt
12
+ }
13
+ imageAlt
14
+ }
15
+ ... on Columns {
16
+ columns {
17
+ __typename
18
+ }
19
+ }
20
+ }
21
+ }
22
+
23
+ query {
24
+ en {
25
+ ...Page
26
+ }
27
+ de {
28
+ ...Page
29
+ }
30
+ }
@@ -0,0 +1,37 @@
1
+ type Query {
2
+ en: Page @loadEntity(type: "node", id: "1")
3
+ de: Page @loadEntity(type: "node", id: "1") @entityTranslation(lang: "de")
4
+ }
5
+
6
+ type Page {
7
+ title: String! @property(path: "title.value")
8
+ content: [Blocks]!
9
+ @resolveEditorBlocks(
10
+ path: "body.value"
11
+ aggregated: ["core/paragraph", "core/list"]
12
+ )
13
+ }
14
+
15
+ union Blocks @resolveEditorBlockType = Text | Figure | Columns
16
+
17
+ union ColumnBlocks @resolveEditorBlockType = Text | Figure
18
+
19
+ type Text @type(id: "core/paragraph") {
20
+ content: String @resolveEditorBlockMarkup
21
+ }
22
+
23
+ type Figure @type(id: "custom/figure") {
24
+ caption: String @resolveEditorBlockAttribute(key: "caption")
25
+ image: Image @resolveEditorBlockMedia
26
+ imageAlt: String
27
+ @resolveEditorBlockMedia
28
+ @property(path: "field_media_image.alt")
29
+ }
30
+
31
+ type Image {
32
+ alt: String @property(path: "field_media_image.alt")
33
+ }
34
+
35
+ type Columns @type(id: "custom/columns") {
36
+ columns: [ColumnBlocks]! @resolveEditorBlockChildren
37
+ }
@@ -0,0 +1,9 @@
1
+ name: Silverback Gutenberg Test Validator
2
+ type: module
3
+ description:
4
+ 'Support module for Silverback Gutenberg validators plugin system testing.'
5
+ package: 'Silverback Testing'
6
+ core_version_requirement: ^8 || ^9 || ^10 || ^11
7
+
8
+ dependencies:
9
+ - silverback_gutenberg:silverback_gutenberg
@@ -0,0 +1,42 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg_test_validator\Plugin\Validation\GutenbergValidator;
4
+
5
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorInterface;
6
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorTrait;
7
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase;
8
+ use Drupal\Core\StringTranslation\StringTranslationTrait;
9
+
10
+ /**
11
+ * Validate test plugin for the ANY cardinality.
12
+ *
13
+ * @GutenbergValidator(
14
+ * id="column_validator",
15
+ * label = @Translation("Column validator"),
16
+ * )
17
+ */
18
+ class ColumnValidator extends GutenbergValidatorBase {
19
+
20
+ use GutenbergCardinalityValidatorTrait;
21
+ use StringTranslationTrait;
22
+
23
+ /**
24
+ * {@inheritDoc}
25
+ */
26
+ public function applies(array $block): bool {
27
+ return $block['blockName'] === 'core/column';
28
+ }
29
+
30
+ /**
31
+ * {@inheritDoc}
32
+ */
33
+ public function validateContent(array $block = []): array {
34
+ $expectedChildren = [
35
+ 'validationType' => GutenbergCardinalityValidatorInterface::CARDINALITY_ANY,
36
+ 'min' => 1,
37
+ 'max' => 2,
38
+ ];
39
+ return $this->validateCardinality($block, $expectedChildren);
40
+ }
41
+
42
+ }
@@ -0,0 +1,50 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg_test_validator\Plugin\Validation\GutenbergValidator;
4
+
5
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorTrait;
6
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase;
7
+ use Drupal\Core\StringTranslation\StringTranslationTrait;
8
+
9
+ /**
10
+ * Validate test plugin for multiple types of inner blocks.
11
+ *
12
+ * @GutenbergValidator(
13
+ * id="group_validator",
14
+ * label = @Translation("Group validator"),
15
+ * )
16
+ */
17
+ class GroupValidator extends GutenbergValidatorBase {
18
+
19
+ use GutenbergCardinalityValidatorTrait;
20
+ use StringTranslationTrait;
21
+
22
+ /**
23
+ * {@inheritDoc}
24
+ */
25
+ public function applies(array $block): bool {
26
+ return $block['blockName'] === 'core/group';
27
+ }
28
+
29
+ /**
30
+ * {@inheritDoc}
31
+ */
32
+ public function validateContent(array $block = []): array {
33
+ $expectedChildren = [
34
+ [
35
+ 'blockName' => 'core/paragraph',
36
+ 'blockLabel' => $this->t('Paragraph'),
37
+ 'min' => 1,
38
+ 'max' => 2,
39
+ ],
40
+ [
41
+ 'blockName' => 'core/list',
42
+ 'blockLabel' => $this->t('List'),
43
+ 'min' => 1,
44
+ 'max' => 1,
45
+ ],
46
+ ];
47
+ return $this->validateCardinality($block, $expectedChildren);
48
+ }
49
+
50
+ }
@@ -0,0 +1,43 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg_test_validator\Plugin\Validation\GutenbergValidator;
4
+
5
+ use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase;
6
+ use Drupal\Core\StringTranslation\StringTranslationTrait;
7
+
8
+ /**
9
+ * Validate test plugin to check if a linkUrl and linkLabel attribute exists.
10
+ *
11
+ * @GutenbergValidator(
12
+ * id="link_validator",
13
+ * label = @Translation("Link URL"),
14
+ * )
15
+ */
16
+ class LinkValidator extends GutenbergValidatorBase {
17
+
18
+ use StringTranslationTrait;
19
+
20
+ /**
21
+ * {@inheritDoc}
22
+ */
23
+ public function applies(array $block): bool {
24
+ return $block['blockName'] === 'custom/link';
25
+ }
26
+
27
+ /**
28
+ * {@inheritDoc}
29
+ */
30
+ public function validatedFields($block = []): array {
31
+ return [
32
+ 'linkUrl' => [
33
+ 'field_label' => $this->t('Link URL'),
34
+ 'rules' => ['required']
35
+ ],
36
+ 'linkLabel' => [
37
+ 'field_label' => $this->t('Link Label'),
38
+ 'rules' => ['required']
39
+ ],
40
+ ];
41
+ }
42
+
43
+ }
@@ -0,0 +1,194 @@
1
+ <?php
2
+
3
+ namespace Drupal\Tests\silverback_gutenberg\Kernel;
4
+
5
+ use Drupal\KernelTests\KernelTestBase;
6
+ use Drupal\node\Entity\Node;
7
+ use Drupal\silverback_gutenberg\Plugin\Validation\Constraint\Gutenberg;
8
+ use Drupal\silverback_gutenberg\Plugin\Validation\Constraint\GutenbergValidator;
9
+ use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
10
+ use Drupal\Tests\silverback_gutenberg\Traits\SampleAssetTrait;
11
+ use Symfony\Component\Validator\Context\ExecutionContextInterface;
12
+
13
+ class BlockValidationRuleTest extends KernelTestBase {
14
+
15
+ use ContentTypeCreationTrait;
16
+ use SampleAssetTrait;
17
+
18
+ const FIELD_NAME = 'field_test';
19
+
20
+ protected static $modules = [
21
+ 'system',
22
+ 'path_alias',
23
+ 'field',
24
+ 'node',
25
+ 'user',
26
+ 'text',
27
+ 'silverback_gutenberg',
28
+ 'silverback_gutenberg_test_validator',
29
+ ];
30
+
31
+ /**
32
+ * @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleManager
33
+ */
34
+ protected $validatorRulePluginManager;
35
+
36
+ /**
37
+ * @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorManager
38
+ */
39
+ protected $validatorPluginManager;
40
+
41
+ protected function setUp(): void {
42
+ parent::setUp();
43
+
44
+ $this->installSchema('system', 'sequences');
45
+ $this->installEntitySchema('node');
46
+ $this->installEntitySchema('user');
47
+ $this->installConfig('node');
48
+ $this->createContentType([
49
+ 'type' => 'page',
50
+ 'name' => 'Basic page'
51
+ ]);
52
+
53
+ $this->validatorRulePluginManager = \Drupal::service('plugin.manager.gutenberg_validator_rule');
54
+ $this->validatorPluginManager = \Drupal::service('plugin.manager.gutenberg_validator');
55
+ }
56
+
57
+ public function testEmailValidatorRule(): void {
58
+ /** @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface $emailValidator */
59
+ $emailValidator = $this->validatorRulePluginManager->createInstance('email');
60
+ $this->assertTrue($emailValidator->validate('test@example', self::FIELD_NAME));
61
+ $this->assertTrue($emailValidator->validate('test@example.com', self::FIELD_NAME));
62
+ $this->assertEquals($emailValidator->validate('test@', self::FIELD_NAME), '<em class="placeholder">' . self::FIELD_NAME . '</em> is not valid.');
63
+ }
64
+
65
+ public function testRequiredValidatorRule(): void {
66
+ $requiredMessage = '<em class="placeholder">' . self::FIELD_NAME . '</em> field is required.';
67
+ /** @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface $requiredValidator */
68
+ $requiredValidator = $this->validatorRulePluginManager->createInstance('required');
69
+ $this->assertTrue($requiredValidator->validate('banana', self::FIELD_NAME));
70
+ $this->assertEquals($requiredValidator->validate(NULL, self::FIELD_NAME), $requiredMessage);
71
+ $this->assertEquals($requiredValidator->validate('', self::FIELD_NAME), $requiredMessage);
72
+ $this->assertEquals($requiredValidator->validate('_none', self::FIELD_NAME), $requiredMessage);
73
+ }
74
+
75
+ /**
76
+ * Limit test to content validator only.
77
+ *
78
+ * More specific cardinality tests are covered by BlocksValidatorCardinalityTest.
79
+ *
80
+ * @return void
81
+ */
82
+ public function testContentValidator(): void {
83
+ // Validates any blocks.
84
+ /** @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorInterface $columnValidator */
85
+ $columnValidator = $this->validatorPluginManager->createInstance('column_validator');
86
+ $columnBlock = [
87
+ 'blockName' => 'core/column',
88
+ 'innerBlocks' => [
89
+ [
90
+ 'blockName' => 'core/paragraph',
91
+ ],
92
+ [
93
+ 'blockName' => 'core/list',
94
+ ],
95
+ ],
96
+ ];
97
+ $this->assertEquals($columnValidator->validateContent($columnBlock), ['is_valid' => TRUE, 'message' => '']);
98
+
99
+ // Validates multiple specific blocks.
100
+ $groupValidator = $this->validatorPluginManager->createInstance('group_validator');
101
+ $groupBlock = [
102
+ 'blockName' => 'core/group',
103
+ 'innerBlocks' => [
104
+ [
105
+ 'blockName' => 'core/paragraph',
106
+ ],
107
+ [
108
+ 'blockName' => 'core/paragraph',
109
+ ],
110
+ [
111
+ 'blockName' => 'core/list',
112
+ ],
113
+ ],
114
+ ];
115
+ $this->assertEquals($groupValidator->validateContent($groupBlock), ['is_valid' => TRUE, 'message' => '']);
116
+ }
117
+
118
+ /**
119
+ * @dataProvider providerTestFieldValidator
120
+ */
121
+ public function testFieldValidator(string $body, array $expected_violations): void {
122
+ $context = $this->prophesize(ExecutionContextInterface::class);
123
+ $constraintValidator = new GutenbergValidator($this->validatorPluginManager, $this->validatorRulePluginManager);
124
+ $constraintValidator->initialize($context->reveal());
125
+
126
+ $node = Node::create([
127
+ 'type' => 'page',
128
+ 'title' => 'Test',
129
+ 'body' => $body,
130
+ ]);
131
+ $node->save();
132
+ $constraintValidator->validate($node->get('body'), new Gutenberg());
133
+ $violations = $this->getViolations($constraintValidator);
134
+ $this->assertEquals($violations, $expected_violations);
135
+ }
136
+
137
+ /**
138
+ * Data provider for ::testFieldValidator().
139
+ */
140
+ public function providerTestFieldValidator() {
141
+ return [
142
+ [
143
+ 'body' => '<!-- wp:custom/link {"linkLabel":"test","linkUrl":"https://example.com"} /-->',
144
+ 'violations' => [],
145
+ ],
146
+ [
147
+ 'body' => '<!-- wp:custom/link {"linkLabel":"test","linkUrl":""} /-->',
148
+ 'violations' => [
149
+ [
150
+ 'attribute' => 'linkUrl',
151
+ 'blockName' => 'custom/link',
152
+ 'rule' => 'required',
153
+ 'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link URL</em> field is required.</span>',
154
+ ],
155
+ ]
156
+ ],
157
+ [
158
+ 'body' => '<!-- wp:custom/link {"linkLabel":"","linkUrl":"https://example.com"} /-->',
159
+ 'violations' => [
160
+ [
161
+ 'attribute' => 'linkLabel',
162
+ 'blockName' => 'custom/link',
163
+ 'rule' => 'required',
164
+ 'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link Label</em> field is required.</span>'
165
+ ],
166
+ ]
167
+ ],
168
+ [
169
+ 'body' => '<!-- wp:custom/link {"linkLabel":"","linkUrl":""} /-->',
170
+ 'violations' => [
171
+ [
172
+ 'attribute' => 'linkUrl',
173
+ 'blockName' => 'custom/link',
174
+ 'rule' => 'required',
175
+ 'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link URL</em> field is required.</span>',
176
+ ],
177
+ [
178
+ 'attribute' => 'linkLabel',
179
+ 'blockName' => 'custom/link',
180
+ 'rule' => 'required',
181
+ 'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link Label</em> field is required.</span>'
182
+ ],
183
+ ]
184
+ ]
185
+ ];
186
+ }
187
+
188
+ private function getViolations(GutenbergValidator $validator) {
189
+ $reflection = new \ReflectionClass($validator);
190
+ $property = $reflection->getProperty('violations');
191
+ return $property->getValue($validator);
192
+ }
193
+
194
+ }