summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/ckeditor5/ckeditor5.api.php
blob: 4f13d953cdd683788d85698121bf7a91345f9298 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
<?php

/**
 * @file
 * Documentation related to CKEditor 5.
 */

use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;

/**
 * @defgroup ckeditor5_architecture CKEditor 5 architecture
 * @{
 *
 * @section overview Overview
 * The CKEditor 5 module integrates CKEditor 5 with Drupal's filtering and text
 * editor APIs.
 *
 * Where possible, it uses upstream CKEditor plugins, but it also relies on
 * Drupal-specific CKEditor plugins to ensure a consistent user experience.
 *
 * @see https://ckeditor.com/ckeditor-5/
 *
 * @section data_models Data models
 * Drupal and CKEditor 5 have very different data models.
 *
 * Drupal stores blobs of HTML that remains manageable thanks to the use of
 * filters and granular HTML restrictions — crucially this remains manageable
 * thanks to those restrictions but also because Drupal does not need to
 * process, render, understand or otherwise interact with it.
 *
 * @see \Drupal\text\Plugin\Field\FieldType\TextItemBase
 * @see \Drupal\filter\Plugin\Filter\FilterInterface::getHTMLRestrictions()
 *
 * On the other hand, CKEditor 5 must not only be able to render these
 * blobs, but also allow editing and creating it. This requires a much deeper
 * understanding of that HTML.
 *
 * CKEditor 5 (in contrast with CKEditor 4) therefore has its own data model to
 * represent this information — that data model is explicitly not HTML.
 *
 * Therefore all interactions between Drupal and CKEditor 5 need to translate
 * between these different data models.
 *
 * @see https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architecture/editing-engine.html#element-types-and-custom-data
 *
 * @section plugins CKEditor 5 Plugins
 * CKEditor 5 plugins may use either YAML or a PHP attribute for their
 * definitions. A PHP class does not need an attribute if it is defined in yml.
 *
 * To be discovered, YAML definition files must be named
 * {module_name}.ckeditor5.yml.
 *
 * @see ckeditor5.ckeditor5.yml for many examples of CKEditor 5 plugin configuration as YAML.
 *
 * The minimally required metadata: the CKEditor 5 plugins to load, the label
 * and the HTML elements it can generate — here's an example for a module
 * providing a Marquee plugin, both in yml or Annotation form:
 *
 * Declared in the yml file:
 * @code
 * # In the MODULE_NAME.ckeditor5.yml file.
 *
 * MODULE_NAME_marquee:
 *   ckeditor5:
 *     plugins: [PACKAGE.CLASS]
 *   drupal:
 *     label: Marquee
 *     library: MODULE_NAME/ckeditor5.marquee
 *     elements:
 *       - <marquee>
 *       - <marquee behavior>
 * @endcode
 *
 * Declared as an Attribute:
 * @code
 * use Drupal\ckeditor5\Attribute\CKEditor5AspectsOfCKEditor5Plugin;
 * use Drupal\ckeditor5\Attribute\CKEditor5Plugin;
 * use Drupal\ckeditor5\Attribute\DrupalAspectsOfCKEditor5Plugin;
 * use Drupal\Core\StringTranslation\TranslatableMarkup;
 *
 * #[CKEditor5Plugin(
 *   id: 'MODULE_NAME_marquee',
 *   ckeditor5: new CKEditor5AspectsOfCKEditor5Plugin(
 *     plugins: ['PACKAGE.CLASS'],
 *   ),
 *   drupal: new DrupalAspectsOfCKEditor5Plugin(
 *     label: new TranslatableMarkup('Marquee'),
 *     library: 'MODULE_NAME/ckeditor5.marquee',
 *     elements: ['<marquee>', '<marquee behavior>'],
 *   ),
 * )]
 * @endcode
 *
 * The metadata relating strictly to the CKEditor 5 plugin's JS code is stored
 * in the 'ckeditor5' key; all other metadata is stored in the 'drupal' key.
 *
 * If the plugin has a dependency on another module, adding the 'provider' key
 * will prevent the plugin from being loaded if that module is not installed.
 *
 * All of these can be defined in YAML or attributes. A given plugin should
 * choose one or the other, as a definition can't parse both at once.
 *
 * Overview of all available plugin definition properties:
 *
 * - provider: Allows a plugin to have a dependency on another module. If it has
 *   a value, a module with a machine name matching that value must be installed
 *   for the configured plugin to load.
 * - ckeditor5.plugins: A list CKEditor 5 JavaScript plugins to load, as
 *   '{package.Class}' , such as 'drupalMedia.DrupalMedia'.
 * - ckeditor5.config: A keyed array of additional values for the constructor of
 *   the CKEditor 5 JavaScript plugins being loaded. i.e. this becomes the
 *   CKEditor 5 plugin configuration settings (see
 *   https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/configuration.html)
 *   for a given plugin.
 * - drupal.label: Human-readable name of the CKEditor 5 plugin.
 * - drupal.library: A Drupal asset library to load with the plugin.
 * - drupal.admin_library: A Drupal asset library that will load in the text
 *   format admin UI when the plugin is available.
 * - drupal.class: Optional PHP class that makes it possible for the plugin to
 *   provide dynamic values, or a configuration UI. The value should be
 *   formatted as '\Drupal\{module_name}\Plugin\CKEditor5Plugin\{class_name}' to
 *   make it discoverable.
 * - drupal.elements: A list of elements and attributes the plugin allows use of
 *   within CKEditor 5. This uses the same syntax as the 'filter_html' plugin
 *   with an additional special keyword: '<$text-container>' . Using
 *   '<$text-container [attribute(s)]>` will permit the provided
 *   attributes in all CKEditor 5's `$block` text container tags that are
 *   explicitly enabled in any plugin. i.e. if only '<p>', '<h3>' and '<h2>'
 *   tags are allowed, then '<$text-container data-something>' will allow the
 *   'data-something' attribute for '<p>', '<h3>' and '<h2>' tags.
 *   Note that while the syntax is the same, some extra nuance is needed:
 *   although this syntax can be used to create an attribute on an element, f.e.
 *   (['<marquee behavior>']) creating the `behavior` attribute on `<marquee>`,
 *   the tag itself must be creatable as well (['<marquee>']). If a plugin wants
 *   the tag and attribute to be created, list both:
 *   (['<marquee>', '<marquee behavior>']). Validation logic ensures that a
 *   plugin supporting only the creation of attributes cannot be enabled if the
 *   tag cannot be created via itself or through another CKEditor 5 plugin.
 * - drupal.toolbar_items: List of toolbar items the plugin provides. Keyed by a
 *   machine name and the value being a pair defining the label:
 *   @code
 *   toolbar_items:
 *     indent:
 *       label: Indent
 *     outdent:
 *       label: Outdent
 *   @encode
 * - drupal.conditions: Conditions required for the plugin to load (other than
 *   module dependencies, which are defined by the 'provider' property).
 *   Conditions can check for five different things:
 *   - 'toolbarItem': a toolbar item that must be enabled
 *   - 'filter': a filter that must be enabled
 *   - 'imageUploadStatus': TRUE if image upload must be enabled, FALSE if it
 *      must not be enabled
 *   - 'requiresConfiguration': a subset of the configuration for this plugin
 *      that must match (exactly)
 *   - 'plugins': a list of CKEditor 5 Drupal plugin IDs that must be enabled
 *   Plugins requiring more complex conditions, such as requiring multiple
 *   toolbar items or multiple filters, have not yet been identified. If this
 *   need arises, see
 *   https://www.drupal.org/docs/drupal-apis/ckeditor-5-api/overview#conditions.
 *
 * All of these can be defined in YAML or attributes. A given plugin should
 * choose one or the other, as a definition can't parse both at once.
 *
 * If the CKEditor 5 plugin contains translation they can be automatically
 * loaded by Drupal by adding the dependency to the core/ckeditor5.translations
 * library to the CKEditor 5 plugin library definition:
 *
 * @code
 * # In the MODULE_NAME.libraries.yml file.
 *
 * marquee:
 *  js:
 *    assets/ckeditor5/marquee/marquee.js: { minified: true }
 *  dependencies:
 *    - core/ckeditor5
 *    - core/ckeditor5.translations
 * @endcode
 *
 * The translations for CKEditor 5 are located in a translations/ subdirectory,
 * Drupal will load the corresponding translation when necessary, located in
 * assets/ckeditor5/marquee/translations/* in this example.
 *
 *
 * @see \Drupal\ckeditor5\Attribute\CKEditor5Plugin
 * @see \Drupal\ckeditor5\Attribute\CKEditor5AspectsOfCKEditor5Plugin
 * @see \Drupal\ckeditor5\Attribute\DrupalAspectsOfCKEditor5Plugin
 *
 * @section public_api Public API
 *
 * The CKEditor 5 module provides no public API, other than:
 * - the attributes and interfaces mentioned above;
 * - to help implement CKEditor 5 plugins:
 *   \Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableTrait and
 *   \Drupal\ckeditor5\Plugin\CKEditor5PluginDefault;
 * - \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition, which is used to
 *   interact with plugin definitions in hook_ckeditor5_plugin_info_alter();
 * - to help contributed modules write tests:
 *   \Drupal\Tests\ckeditor5\Kernel\CKEditor5ValidationTestTrait and
 *   \Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
 * - to help contributed modules write configuration schemas for configurable
 *   plugins, the data types in config/schema/ckeditor5.data_types.yml are
 *   likely to be useful. They automatically get validation constraints applied;
 * - to help contributed modules write validation constraints for configurable
 *   plugins, it is strongly recommended to subclass
 *   \Drupal\Tests\ckeditor5\Kernel\ValidatorsTest. For very complex validation
 *   constraints that need to access text editor and/or format, use
 *   \Drupal\ckeditor5\Plugin\Validation\Constraint\TextEditorObjectDependentValidatorTrait.
 *
 * @}
 */

/**
 * @addtogroup hooks
 * @{
 */

/**
 * Modify the list of available CKEditor 5 plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugin_definitions
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager
 */
function hook_ckeditor5_plugin_info_alter(array &$plugin_definitions): void {
  // Add a link decorator to the link plugin.
  assert($plugin_definitions['ckeditor5_link'] instanceof CKEditor5PluginDefinition);
  $link_plugin_definition = $plugin_definitions['ckeditor5_link']->toArray();
  $link_plugin_definition['ckeditor5']['config']['link']['decorators'][] = [
    'mode' => 'manual',
    'label' => t('Open in new window'),
    'attributes' => [
      'target' => '_blank',
    ],
  ];
  $plugin_definitions['ckeditor5_link'] = new CKEditor5PluginDefinition($link_plugin_definition);

  // Add a custom file type to the image upload plugin. Note that 'tiff' below
  // should be an IANA image media type Name, with the "image/" prefix omitted.
  // In other words: a subtype of type image.
  // @see https://www.iana.org/assignments/media-types/media-types.xhtml#image
  // @see https://ckeditor.com/docs/ckeditor5/latest/api/module_image_imageconfig-ImageUploadConfig.html#member-types
  assert($plugin_definitions['ckeditor5_imageUpload'] instanceof CKEditor5PluginDefinition);
  $image_upload_plugin_definition = $plugin_definitions['ckeditor5_imageUpload']->toArray();
  $image_upload_plugin_definition['ckeditor5']['config']['image']['upload']['types'][] = 'tiff';
  $plugin_definitions['ckeditor5_imageUpload'] = new CKEditor5PluginDefinition($image_upload_plugin_definition);
}

/**
 * @} End of "addtogroup hooks".
 */