@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,114 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\BlockMutator;
4
+
5
+ use Drupal\Core\Cache\CacheBackendInterface;
6
+ use Drupal\Core\Extension\ModuleHandlerInterface;
7
+ use Drupal\Core\Plugin\DefaultPluginManager;
8
+ use Drupal\silverback_gutenberg\BlockMutator\BlockMutatorInterface;
9
+ use Drupal\silverback_gutenberg\Attribute\GutenbergBlockMutator;
10
+
11
+ /**
12
+ * The block mutator plugin manager class.
13
+ */
14
+ class BlockMutatorManager extends DefaultPluginManager implements BlockMutatorManagerInterface {
15
+
16
+ /**
17
+ * Constructs a BlockMutatorManager object.
18
+ *
19
+ * @param \Traversable $namespaces
20
+ * An object that implements \Traversable which contains the root paths
21
+ * keyed by the corresponding namespace to look for plugin implementations.
22
+ * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
23
+ * Cache backend instance to use.
24
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
25
+ * The module handler to invoke the alter hook with.
26
+ */
27
+ public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
28
+ parent::__construct(
29
+ 'Plugin/GutenbergBlockMutator',
30
+ $namespaces,
31
+ $module_handler,
32
+ BlockMutatorInterface::class,
33
+ GutenbergBlockMutator::class,
34
+ // Keeping BC for deprecated Annotation
35
+ 'Drupal\silverback_gutenberg\Annotation\GutenbergBlockMutator',
36
+ );
37
+ $this->alterInfo('gutenberg_block_mutator_info');
38
+ $this->setCacheBackend($cache_backend, 'gutenberg_block_mutator_info_plugins');
39
+ }
40
+
41
+ /**
42
+ * {@inheritDoc}
43
+ */
44
+ public function mutateExport(array &$blocks, array &$dependencies): void {
45
+ $this->mutate('export', $blocks, $dependencies);
46
+ }
47
+
48
+ /**
49
+ * {@inheritDoc}
50
+ */
51
+ public function mutateImport(array &$blocks): void {
52
+ $this->mutate('import', $blocks);
53
+ }
54
+
55
+ /**
56
+ * Mutates a set of gutenberg blocks into a specicif direction
57
+ * (import/export).
58
+ *
59
+ * @param string $direction
60
+ * The direction of the mutation. Can be "import" or "export".
61
+ * @param array $blocks
62
+ * The gutenberg blocks being processed.
63
+ * @param array $dependencies
64
+ * An array of dependencies for these blocks. Each key of the array should be
65
+ * a uuid and the value should be the corresponding entity type. Example:
66
+ * $dependencies['some-uuid'] = 'media';
67
+ */
68
+ protected function mutate(string $direction, array &$blocks, array &$dependencies = []): void {
69
+ if (empty($blocks)) {
70
+ return;
71
+ }
72
+ foreach ($this->getDefinitions() as $definition) {
73
+ /* @var \Drupal\silverback_gutenberg\BlockMutator\BlockMutatorInterface $mutatorPlugin */
74
+ $mutatorPlugin = $this->createInstance($definition['id']);
75
+ $this->doMutate($mutatorPlugin, $direction, $blocks, $dependencies);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Performs a mutation recursively on a set of gutenberg blocks, using a
81
+ * specific mutator plugin, into a specific direction (import / export).
82
+ *
83
+ * @param BlockMutatorInterface $mutatorPlugin
84
+ * A block mutator plugin.
85
+ * @param string $direction
86
+ * The direction of the mutation. Can be "import" or "export". If an invalid
87
+ * string is sent, the "export" value is used.
88
+ * @param array $blocks
89
+ * The gutenberg blocks being processed.
90
+ * @param array $dependencies
91
+ * An array of dependencies for these blocks. Each key of the array should be
92
+ * a uuid and the value should be the corresponding entity type. Example:
93
+ * $dependencies['some-uuid'] = 'media';
94
+ */
95
+ protected function doMutate(BlockMutatorInterface $mutatorPlugin, string $direction, array &$blocks, array &$dependencies = []): void {
96
+ foreach ($blocks as &$block) {
97
+ if ($mutatorPlugin->applies($block)) {
98
+ switch ($direction) {
99
+ case 'import':
100
+ $mutatorPlugin->mutateImport($block);
101
+ break;
102
+ case 'export':
103
+ default:
104
+ $mutatorPlugin->mutateExport($block, $dependencies);
105
+ break;
106
+ }
107
+ }
108
+ if (!empty($block['innerBlocks'])) {
109
+ $this->doMutate($mutatorPlugin, $direction, $block['innerBlocks'], $dependencies);
110
+ }
111
+ }
112
+ }
113
+
114
+ }
@@ -0,0 +1,30 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\BlockMutator;
4
+
5
+ /**
6
+ * Interface for the block mutator plugin manager.
7
+ */
8
+ interface BlockMutatorManagerInterface {
9
+
10
+ /**
11
+ * Mutates the gutenberg blocks before they are exported by the default
12
+ * content module.
13
+ *
14
+ * @param array $blocks
15
+ * The gutenberg blocks being processed.
16
+ * @param array $dependencies
17
+ * An array of dependencies for these blocks. Each key of the array should be
18
+ * a uuid and the value should be the corresponding entity type. Example:
19
+ * $dependencies['some-uuid'] = 'media';
20
+ */
21
+ public function mutateExport(array &$blocks, array &$dependencies): void;
22
+
23
+ /**
24
+ * Mutates the gutenberg blocks before they get imported.
25
+ *
26
+ * @param array $blocks
27
+ * The gutenberg blocks being processed.
28
+ */
29
+ public function mutateImport(array &$blocks): void;
30
+ }
@@ -0,0 +1,189 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\BlockMutator;
4
+
5
+ use Drupal\Core\Entity\ContentEntityInterface;
6
+ use Drupal\Core\Entity\EntityRepositoryInterface;
7
+ use Drupal\Core\Entity\EntityTypeManagerInterface;
8
+ use Drupal\Core\Logger\LoggerChannelFactoryInterface;
9
+ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
10
+ use Drupal\Core\Plugin\PluginBase;
11
+ use Symfony\Component\DependencyInjection\ContainerInterface;
12
+
13
+ abstract class EntityBlockMutatorBase extends PluginBase implements
14
+ BlockMutatorInterface,
15
+ ContainerFactoryPluginInterface {
16
+
17
+ /**
18
+ * The Gutenberg attribute id.
19
+ *
20
+ * @var string $gutenbergAttribute
21
+ */
22
+ public string $gutenbergAttribute;
23
+
24
+ /**
25
+ * Indicates if the Gutenberg attribute is a collection or a single value.
26
+ *
27
+ * @var bool $isMultiple
28
+ */
29
+ public bool $isMultiple = FALSE;
30
+
31
+ /**
32
+ * The Drupal entity type id.
33
+ *
34
+ * @var string $entityTypeId
35
+ */
36
+ public string $entityTypeId;
37
+
38
+ /**
39
+ * EntityBlockMutatorBase constructor.
40
+ *
41
+ * @param \Drupal\Core\Entity\EntityRepositoryInterface $repository
42
+ */
43
+ public function __construct(
44
+ array $configuration,
45
+ $plugin_id,
46
+ $plugin_definition,
47
+ private readonly EntityRepositoryInterface $entityRepository,
48
+ private readonly EntityTypeManagerInterface $entityTypeManager,
49
+ private readonly LoggerChannelFactoryInterface $loggerFactory,
50
+ ) {
51
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
52
+ }
53
+
54
+ /**
55
+ * {@inheritDoc}
56
+ */
57
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
58
+ return new static(
59
+ $configuration,
60
+ $plugin_id,
61
+ $plugin_definition,
62
+ $container->get('entity.repository'),
63
+ $container->get('entity_type.manager'),
64
+ $container->get('logger.factory'),
65
+ );
66
+ }
67
+
68
+ /**
69
+ * {@inheritDoc}
70
+ */
71
+ public function applies(array $block) : bool {
72
+ if (empty($this->gutenbergAttribute)) {
73
+ $this->loggerFactory->get('silverback_gutenberg')->warning(
74
+ $this->t('Block mutator attribute is not set for @class, ignoring.', [
75
+ '@class' => self::class,
76
+ ])
77
+ );
78
+ return FALSE;
79
+ }
80
+
81
+ if (empty($this->entityTypeId)) {
82
+ $this->loggerFactory->get('silverback_gutenberg')->warning(
83
+ $this->t('Block mutator attribute @attribute does not specifiy the entity type id for @class, ignoring.', [
84
+ '@attribute' => $this->gutenbergAttribute,
85
+ '@class' => self::class,
86
+ ])
87
+ );
88
+ return FALSE;
89
+ }
90
+
91
+ $entityTypes = $this->entityTypeManager->getDefinitions();
92
+ if (!array_key_exists($this->entityTypeId, $entityTypes)) {
93
+ throw new \Exception(
94
+ $this->t('Block mutator attribute @attribute does not have a valid entity type @entity_type_id for @class, ignoring.', [
95
+ '@attribute' => $this->gutenbergAttribute,
96
+ '@entity_type_id' => $this->entityTypeId,
97
+ '@class' => self::class,
98
+ ])
99
+ );
100
+ }
101
+
102
+ if (empty($block['attrs'][$this->gutenbergAttribute])) {
103
+ return FALSE;
104
+ }
105
+
106
+ if (
107
+ $this->isMultiple &&
108
+ !is_array($block['attrs'][$this->gutenbergAttribute])
109
+ ) {
110
+ throw new \Exception(
111
+ $this->t('Block mutator attribute @attribute is set to be multiple but is not iterable.', [
112
+ '@attribute' => $this->gutenbergAttribute,
113
+ ])
114
+ );
115
+ }
116
+
117
+ return TRUE;
118
+ }
119
+
120
+ /**
121
+ * {@inheritDoc}
122
+ */
123
+ public function mutateExport(array &$block, array &$dependencies) : void {
124
+ if ($this->isMultiple) {
125
+ $block['attrs'][$this->gutenbergAttribute] = array_values(array_map(
126
+ function (ContentEntityInterface $entity) use (&$dependencies) {
127
+ $dependencies[$entity->uuid()] = $this->entityTypeId;
128
+ return $entity->uuid();
129
+ },
130
+ $this->entityRepository
131
+ ->getCanonicalMultiple($this->entityTypeId, $block['attrs'][$this->gutenbergAttribute])
132
+ ));
133
+ } else {
134
+ $entity = $this->entityRepository->getCanonical($this->entityTypeId, $block['attrs'][$this->gutenbergAttribute]);
135
+ if (!$entity instanceof ContentEntityInterface) {
136
+ $block['attrs'][$this->gutenbergAttribute] = '';
137
+ return;
138
+ }
139
+ $block['attrs'][$this->gutenbergAttribute] = $entity->uuid();
140
+ $dependencies[$entity->uuid()] = $this->entityTypeId;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * {@inheritDoc}
146
+ */
147
+ public function mutateImport(array &$block) : void {
148
+ if ($this->isMultiple) {
149
+ $block['attrs'][$this->gutenbergAttribute] = array_map(
150
+ function (string $uuid) {
151
+ try {
152
+ $entity = $this->entityRepository->loadEntityByUuid($this->entityTypeId, $uuid);
153
+ return $entity->id();
154
+ }
155
+ catch (\Throwable $e) {
156
+ $this->loggerFactory->get('silverback_gutenberg')->warning(
157
+ $this->t(
158
+ '@class: Could not load @entity_type_id by uuid @uuid on import.', [
159
+ '@class' => self::class,
160
+ '@entity_type_id' => $this->entityTypeId,
161
+ '@uuid' => $uuid,
162
+ ]
163
+ )
164
+ );
165
+ return $uuid;
166
+ }
167
+ },
168
+ $block['attrs'][$this->gutenbergAttribute]
169
+ );
170
+ } else {
171
+ try {
172
+ $entity = $this->entityRepository->loadEntityByUuid($this->entityTypeId, $block['attrs'][$this->gutenbergAttribute]);
173
+ $block['attrs'][$this->gutenbergAttribute] = $entity->id();
174
+ }
175
+ catch (\Throwable $e) {
176
+ $this->loggerFactory->get('silverback_gutenberg')->warning(
177
+ $this->t(
178
+ '@class: Could not load @entity_type_id by uuid @uuid on import.', [
179
+ '@class' => self::class,
180
+ '@entity_type_id' => $this->entityTypeId,
181
+ '@uuid' => $uuid,
182
+ ]
183
+ )
184
+ );
185
+ }
186
+ }
187
+ }
188
+
189
+ }
@@ -0,0 +1,84 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg;
4
+
5
+ /**
6
+ * Class BlockSerializer
7
+ *
8
+ * Copied from:
9
+ * https://core.trac.wordpress.org/browser/tags/5.3.1/src/wp-includes/blocks.php#L226
10
+ *
11
+ * @package Drupal\silverback_gutenberg
12
+ */
13
+ class BlockSerializer {
14
+
15
+ public function serialize_blocks( $blocks ) {
16
+ return implode( '', array_map( fn ($block) => $this->serialize_block($block), $blocks ) );
17
+ }
18
+
19
+ protected function serialize_block( $block ) {
20
+ $block_content = '';
21
+
22
+ $index = 0;
23
+ foreach ( $block['innerContent'] as $chunk ) {
24
+ $block_content .= is_string( $chunk ) ? $chunk : $this->serialize_block( $block['innerBlocks'][ $index++ ] );
25
+ }
26
+
27
+ if ( ! is_array( $block['attrs'] ) ) {
28
+ $block['attrs'] = array();
29
+ }
30
+
31
+ return $this->get_comment_delimited_block_content(
32
+ $block['blockName'],
33
+ $block['attrs'],
34
+ $block_content
35
+ );
36
+ }
37
+
38
+ protected function get_comment_delimited_block_content($block_name = null, $block_attributes = null, $block_content = '') {
39
+ if ( is_null( $block_name ) ) {
40
+ return $block_content;
41
+ }
42
+
43
+ $serialized_block_name = $this->strip_core_block_namespace(
44
+ $block_name
45
+ );
46
+ $serialized_attributes = empty( $block_attributes ) ? '' : $this->serialize_block_attributes(
47
+ $block_attributes
48
+ ) . ' ';
49
+
50
+ if ( empty( $block_content ) ) {
51
+ return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes );
52
+ }
53
+
54
+ return sprintf(
55
+ '<!-- wp:%s %s-->%s<!-- /wp:%s -->',
56
+ $serialized_block_name,
57
+ $serialized_attributes,
58
+ $block_content,
59
+ $serialized_block_name
60
+ );
61
+ }
62
+
63
+ protected function strip_core_block_namespace( $block_name = null ) {
64
+ if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) {
65
+ return substr( $block_name, 5 );
66
+ }
67
+
68
+ return $block_name;
69
+ }
70
+
71
+
72
+ protected function serialize_block_attributes( $block_attributes ) {
73
+ // TODO: Original wordpress serializer does not unescape slashes.
74
+ $encoded_attributes = json_encode( $block_attributes, JSON_UNESCAPED_SLASHES );
75
+ $encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes );
76
+ $encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes );
77
+ $encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes );
78
+ $encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes );
79
+ // Regex: /\\"/
80
+ $encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes );
81
+
82
+ return $encoded_attributes;
83
+ }
84
+ }
@@ -0,0 +1,84 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\Controller;
4
+
5
+ use Drupal\Component\Serialization\Json;
6
+ use Drupal\Core\Entity\EntityInterface;
7
+ use Drupal\gutenberg\Controller\SearchController;
8
+ use Drupal\linkit\Controller\AutocompleteController;
9
+ use Symfony\Component\HttpFoundation\JsonResponse;
10
+ use Symfony\Component\HttpFoundation\Request;
11
+
12
+ class LinkitAutocomplete extends SearchController {
13
+
14
+ public function search(Request $request): JsonResponse {
15
+ // As we're limited on changes we can make to the Gutenberg plugin, we're
16
+ // using the subtype to identify the linkit profile to use. With it defaulting
17
+ // to a linkit profile with id gutenberg. By default, it's set to the string "undefined", so we
18
+ // need to check for that as well.
19
+ $linkitProfileIdParam = $request->query->get('subtype');
20
+ $linkitProfileId = $linkitProfileIdParam !== "undefined" && $linkitProfileIdParam ? $linkitProfileIdParam : 'gutenberg';
21
+
22
+ if (
23
+ !\Drupal::moduleHandler()->moduleExists('linkit') ||
24
+ !($linkitProfile = \Drupal::entityTypeManager()->getStorage('linkit_profile')->load($linkitProfileId))
25
+ ) {
26
+ return parent::search($request);
27
+ }
28
+
29
+ $type = (string) $request->query->get('type');
30
+ if ($type !== 'post') {
31
+ return new JsonResponse([]);
32
+ }
33
+
34
+ $result = [];
35
+ $search = (string) $request->query->get('search');
36
+ $request->query->set('q', $search);
37
+ $linkitController = AutocompleteController::create(\Drupal::getContainer());
38
+ $response = $linkitController->autocomplete($request, $linkitProfile);
39
+ $rows = Json::decode($response->getContent())['suggestions'] ?? [];
40
+ /** @var \Drupal\linkit\SubstitutionManagerInterface $substitutionManager */
41
+ $substitutionManager = \Drupal::service('plugin.manager.linkit.substitution');
42
+ /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository */
43
+ $entityRepository = \Drupal::service('entity.repository');
44
+ $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
45
+ foreach ($rows as $row) {
46
+ $url = NULL;
47
+ $entity = NULL;
48
+ if (isset($row['entity_type_id'], $row['entity_uuid'])) {
49
+ if ($entity = $entityRepository->loadEntityByUuid($row['entity_type_id'], $row['entity_uuid'])) {
50
+ $entity = $entityRepository->getTranslationFromContext($entity, $langcode);
51
+ if (isset($row['substitution_id'])) {
52
+ $url = $substitutionManager
53
+ ->createInstance($row['substitution_id'])
54
+ ->getUrl($entity)
55
+ ->toString();
56
+ }
57
+ }
58
+ }
59
+ $result[] = [
60
+ 'id' => $row['entity_uuid'],
61
+ 'title' => $row['label'],
62
+ 'type' => $entity
63
+ ? $this->getBundle($entity)
64
+ : ($row['entity_type_id'] ?? $row['description'] ?? NULL),
65
+ 'url' => $url ?? $row['path'],
66
+ ];
67
+ }
68
+
69
+ return new JsonResponse($result);
70
+ }
71
+
72
+ protected function getBundle(EntityInterface $entity): string {
73
+ $entityType = $entity->getEntityType();
74
+ $bundle = NULL;
75
+ if ($entityType->hasKey('bundle')) {
76
+ $bundleKey = $entityType->getKey('bundle');
77
+ $bundle = $entity->get($bundleKey)->entity;
78
+ }
79
+ return $bundle
80
+ ? "{$entityType->getLabel()}: {$bundle->label()}"
81
+ : "{$entityType->getLabel()}";
82
+ }
83
+
84
+ }
@@ -0,0 +1,74 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg;
4
+
5
+ use Drupal\Core\Entity\EntityInterface;
6
+ use Drupal\graphql\GraphQL\Resolver\ResolverInterface;
7
+ use Drupal\graphql\GraphQL\ResolverBuilder;
8
+
9
+ /**
10
+ * Custom directives for silverback compatibility.
11
+ */
12
+ class Directives {
13
+
14
+ /**
15
+ * Extract gutenberg blocks from a page.
16
+ */
17
+ public static function editorBlocks(ResolverBuilder $builder) : ResolverInterface {
18
+ return $builder->produce('editor_blocks', [
19
+ 'path' => $builder->fromArgument('path'),
20
+ 'entity' => $builder->fromParent(),
21
+ 'type' => $builder->compose(
22
+ $builder->fromParent(),
23
+ $builder->callback(
24
+ fn(EntityInterface $entity) =>
25
+ $entity->getTypedData()->getDataDefinition()->getDataType()
26
+ )),
27
+ 'ignored' => $builder->fromArgument('ignored') ?? $builder->fromValue([]),
28
+ 'aggregated' => $builder->fromArgument('aggregated') ?? $builder->fromValue(['core/paragraph']),
29
+ ]);
30
+ }
31
+
32
+ /**
33
+ * Retrieve a blocks inner-blocks.
34
+ */
35
+ public static function editorBlockChildren(ResolverBuilder $builder) : ResolverInterface {
36
+ return $builder->produce('editor_block_children')
37
+ ->map('block', $builder->fromParent());
38
+ }
39
+
40
+ /**
41
+ * Retrieve a blocks markup.
42
+ */
43
+ public static function editorBlockMarkup(ResolverBuilder $builder) : ResolverInterface {
44
+ return $builder->produce('editor_block_html')
45
+ ->map('block', $builder->fromParent());
46
+ }
47
+
48
+ /**
49
+ * Retrieve a blocks media entity.
50
+ */
51
+ public static function editorBlockMedia(ResolverBuilder $builder) : ResolverInterface {
52
+ return $builder->produce('editor_block_media')
53
+ ->map('block', $builder->fromParent());
54
+ }
55
+
56
+ /**
57
+ * Retrieve a blocks type.
58
+ */
59
+ public static function editorBlockType(ResolverBuilder $builder) : ResolverInterface {
60
+ return $builder->produce('editor_block_type')
61
+ ->map('block', $builder->fromParent());
62
+ }
63
+
64
+ /**
65
+ * Extract an attribute from a gutenberg block.
66
+ */
67
+ public static function editorBlockAttribute(ResolverBuilder $builder) : ResolverInterface {
68
+ return $builder->produce('editor_block_attribute')
69
+ ->map('block', $builder->fromParent())
70
+ ->map('name', $builder->fromArgument('key'))
71
+ ->map('plainText', $builder->fromArgument('plainText') ?? $builder->fromValue(TRUE));
72
+ }
73
+
74
+ }
@@ -0,0 +1,53 @@
1
+ <?php
2
+ namespace Drupal\silverback_gutenberg;
3
+
4
+ class EditorBlocksProcessor {
5
+
6
+ static function aggregateParagraphs(array $blocks, ?array $types = ['core/paragraph']) {
7
+ $processed = [];
8
+ $content = [];
9
+ foreach ($blocks as $block) {
10
+ if (in_array($block['blockName'], $types)) {
11
+ $content[] = $block['innerHTML'];
12
+ }
13
+ else {
14
+ if (count($content)) {
15
+ $processed[] = [
16
+ 'blockName' => 'core/paragraph',
17
+ 'innerHTML' => implode('', $content),
18
+ ];
19
+ $content = [];
20
+ }
21
+ if ($block['innerBlocks']) {
22
+ $block['innerBlocks'] = static::aggregateParagraphs($block['innerBlocks'], $types);
23
+ }
24
+ $processed[] = $block;
25
+ }
26
+ }
27
+ if (count($content)) {
28
+ $processed[] = [
29
+ 'blockName' => 'core/paragraph',
30
+ 'innerHTML' => implode('', $content),
31
+ ];
32
+ }
33
+ return $processed;
34
+ }
35
+
36
+ static function processsIgnoredBlocks(array $blocks, ?array $ignored) {
37
+ if (empty($ignored)) {
38
+ return $blocks;
39
+ }
40
+ $processed = [];
41
+ foreach (array_filter($blocks, fn ($block) => !!$block['blockName']) as $block) {
42
+ if (in_array($block['blockName'], $ignored)) {
43
+ foreach ((static::processsIgnoredBlocks($block['innerBlocks'] ?? [], $ignored)) as $child) {
44
+ $processed[] = $child;
45
+ }
46
+ }
47
+ else {
48
+ $processed[] = $block;
49
+ }
50
+ }
51
+ return $processed;
52
+ }
53
+ }
@@ -0,0 +1,19 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gutenberg\GutenbergValidation;
4
+
5
+ interface GutenbergCardinalityValidatorInterface {
6
+
7
+ /**
8
+ * @var int
9
+ * Value that can be used for the maximum.
10
+ */
11
+ const CARDINALITY_UNLIMITED = -1;
12
+
13
+ /**
14
+ * @var string
15
+ * Specifies if the cardinality check is not limited to a given block type.
16
+ */
17
+ const CARDINALITY_ANY = 'any';
18
+
19
+ }