Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dfcbfe1
Update casting
JamesDPC Aug 4, 2025
69114a2
Declare editor_options configuration
JamesDPC Aug 4, 2025
0bc8e36
Allow allowed tags to be passed to clean() as an option
JamesDPC Aug 4, 2025
c4533be
Only add href allowed attribute if 'a' element is listed in allowed e…
JamesDPC Aug 4, 2025
06b03cb
Improve configuration to pull tagsToKeep from ContentSanitiser config…
JamesDPC Aug 4, 2025
d241e4f
Fix failing tests, remove href from expected attributes as no 'a' tag…
JamesDPC Aug 4, 2025
5f30348
(chore) update template
JamesDPC Nov 11, 2025
d6fa371
Change option value removeFormatPasted to allow pasted formats
JamesDPC Nov 11, 2025
367adca
Change option value: resetCss - avoid editor style override
JamesDPC Nov 11, 2025
a80104f
Remove h2 from tags_to_remove default configuration
JamesDPC Nov 11, 2025
eb9b781
Simplify the configuration - store allowed tags as an array. Set conf…
JamesDPC Nov 11, 2025
45dbb6b
Handle empty value prior
JamesDPC Nov 11, 2025
f8a8c5a
Qualify that href belongs to 'a'
JamesDPC Nov 11, 2025
08b8724
Allow style attribute on lists to assist in preserving list style types
JamesDPC Nov 11, 2025
b4fc465
Update test to include 'a.href' inclusion in allowed attributes
JamesDPC Nov 11, 2025
b51b0fc
Allow custom field implementations to provide plugins
JamesDPC Nov 12, 2025
f08f0d5
Move custom allowed attributes and CSS properties to configuration, u…
JamesDPC Nov 12, 2025
95b0714
[rector] Automated updates generated by rector configuration
JamesDPC Nov 12, 2025
62a6544
[php-cs-fixer] Automated updates generated by php-cs-fixer configuration
JamesDPC Nov 12, 2025
668f326
Fix: incorrect application of allowed attributes
JamesDPC Nov 12, 2025
8d28b3b
(docs) minor - fix typo
JamesDPC Nov 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ Please use dedicated upload fields for handling file uploads.

Per [composer.json](/composer.json):

+ silverstripe/framework ^4.10.0
+ jQuery 3.6.0
+ silverstripe/framework ^5
+ jQuery 3.7

The field pulls in required Trumbowyg JS and CSS assets from [cdnjs.com](https://cdnjs.com) along with their respective Sub Resource Integrity (SRI) hashes.
The field pulls in required Trumbowyg JS and CSS assets from `cdn.jsdelivr.net` along with their respective Sub Resource Integrity (SRI) hashes.

If you wish to use your own jQuery, set the `TrumboywgEditorField.use_own_jquery` configuration value to `false` in your project configuration. When false, the module will not include its own jQuery.

Expand Down Expand Up @@ -53,14 +53,18 @@ See [config.yml](./_config/config.yml) for module configuration values

## Maintainers

+ [dpcdigital@NSWDPC:~$](https://dpc.nsw.gov.au)
+ PD Web Team

## Bugtracker

We welcome bug reports, pull requests and feature requests on the Github Issue tracker for this project.

Please review the [code of conduct](./code-of-conduct.md) prior to opening a new issue.

## Security

If you have found a security issue with this module, please email digital[@]dpc.nsw.gov.au in the first instance, detailing your findings.

## Development and contribution

If you would like to make contributions to the module please ensure you raise a pull request and discuss with the module maintainers.
Expand Down
89 changes: 36 additions & 53 deletions _config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ Name: nswdpc-trumbowyg-editor
---
NSWDPC\Utilities\Trumbowyg\TrumbowygEditorField:
# warning: prior to modifying these, read the README.md
# see ContentSanitiser.default_allowed_html_tags for 'tagsToKeep'
# see self.tags_to_remove for 'tagsToRemove'
editor_options:
fixedBtnPane: true
semantic: true
removeformatPasted: true
resetCss: true
removeformatPasted: false
resetCss: false
autogrow: true
btns:
-
Expand All @@ -30,54 +32,35 @@ NSWDPC\Utilities\Trumbowyg\TrumbowygEditorField:
- removeformat
-
- fullscreen
# tags to remove from the editor
tagsToRemove:
- applet
- area
- base
- body
- button
- details
- embed
- form
- frame
- frameset
- head
- html
- iframe
- img
- input
- link
- maction
- map
- math
- object
- option
- picture
- script
- select
- source
- style
- svg
- textarea
- video
- h1
- h2
tagsToKeep:
- p
- i
- blockquote
- b
- strong
- em
- br
- h2
- h3
- h4
- h5
- h6
- ol
- ul
- li
- a
- strike
# tags to remove from the editor
tags_to_remove:
- applet
- area
- base
- body
- button
- details
- embed
- form
- frame
- frameset
- head
- html
- iframe
- img
- input
- link
- maction
- map
- math
- object
- option
- picture
- script
- select
- source
- style
- svg
- textarea
- video
- h1
34 changes: 34 additions & 0 deletions client/static/js/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Trumbowyg editor loader
*/
class TrumbowygLoader {

handle() {

let editors = document.querySelectorAll('textarea[data-tw="1"]');
editors.forEach(
editor => {
this.setupEditor(editor);
}
);
}

setupEditor(editor) {
let options = JSON.parse(editor.dataset.twOptions);
jQuery(editor).trumbowyg(options).on(
'tbwblur',
function(e) {
try {
let el = new DOMParser().parseFromString($(this).val(), 'text/html');
let txt = el.documentElement.textContent.trim();
if(txt == '') {
jQuery(this).val('');
}
} catch (e) {
console.warn('Could not parse value');
}
}
);
}

}
27 changes: 4 additions & 23 deletions docs/en/001_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,17 @@ Additionally, "javascript:" is removed from the href attribute

## Tag restrictions

By default the following tags are allowed in the editor (see _config/config.yml)

```yml
- p
- i
- blockquote
- b
- strong
- em
- br
- h3
- h4
- h5
- h6
- ol
- ul
- li
- a
- strike
```
See [config.yml](../../_config/config.yml) for the current list of tags allowed

Only the `href` attribute is allowed (for links), with http or https schemes.
Only the `href` attribute is allowed (for `a`), with http or https schemes.

If no configuration value `tagsToKeep` is available or it is empty, a default set is used. The fallback condition is to restrict to `<p>` tags only.

The editor is provided a set of `tagsToRemove` for client-side editing (see _config/config.yml). This configuration is not used in saving the value, as value saving is determined by the `tagsToKeep` only.
The editor is provided a set of `tagsToRemove` for client-side editing [in config.yml](../../_config/config.yml). This configuration is not used in saving the value, as value saving is determined by the `tagsToKeep` only.

## Options

If no configuration is provided, the default configuration defined in [TrumbowygEditorField::getFieldOptions()](../../src/Fields/TrumbowygEditorField.php) is used.
If no editor configuration is provided, an `InvalidArgumentException` is thrown.

## Basic example

Expand Down
118 changes: 62 additions & 56 deletions src/Fields/TrumbowygEditorField.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,60 @@
namespace NSWDPC\Utilities\Trumbowyg;

use SilverStripe\Forms\TextareaField;
use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements;

class TrumbowygEditorField extends TextareaField
{
private static array $casting = [
'Value' => 'HTMLText',
'Value' => 'HTMLFragment',
];

private static bool $include_own_jquery = true;

/**
* See _config.yml for default editor options
*/
private static array $editor_options = [];

/**
* Get field options
* @return array
*/
protected function getFieldOptions()
protected function getFieldOptions(): array
{
$options = $this->config()->get('editor_options');
// default options
$options = static::config()->get('editor_options');
if (empty($options) || !is_array($options)) {
// Fallback options in case of none configured
$options = [
"fixedBtnPane" => true,
"semantic" => true,
"removeformatPasted" => true,
"resetCss" => true,
"autogrow" => true,
"btns" => [
[ "undo", "redo" ],
[ "p", "h2","h3", "h4", "h5", "strong", "em" ],
[ "link", "" ],
[ "unorderedList", "orderedList" ],
[ "removeformat" ],
[ "fullscreen" ]
],
"tagsToKeep" => [
"p",
"i","b", "strong", "em", "br",
"h2","h3","h4","h5","h6",
"ol","ul","li","a"
]
];
throw new \InvalidArgumentException("Missing or invalid editor_options configuration");
}

// keep these tags
$options['tagsToKeep'] = ContentSanitiser::getAllowedHTMLTags();
// remove these tags from the editor
$options['tagsToRemove'] = self::getDeniedTags();
return $options;
}

/**
* These tags are denied by default
*
*/
public static function getDeniedTags(): array
{
return [
'form',
'script',
'link',
'style',
'body',
'html',
'head',
'meta',
'applet',
'object',
'iframe',
'img',
'picture',
'video',
];
$tags = static::config()->get('tags_to_remove');
if (!is_array($tags)) {
return [];
} else {
return $tags;
}
}

/**
* Add requirements for plugins
* Use Injector to provide a custom implementation of this field with plugins
*/
protected function addTrumbowygPluginRequirements(): void
{
// NOOP
}

/**
Expand All @@ -80,8 +66,9 @@ public static function getDeniedTags(): array
public function Field($properties = [])
{
$this->setAttribute('data-tw', '1');
$this->setAttribute('data-tw-options', json_encode($this->getFieldOptions()));

if ($this->config()->get('include_own_jquery')) {
if (static::config()->get('include_own_jquery')) {
Requirements::javascript(
"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js",
[
Expand All @@ -98,15 +85,9 @@ public function Field($properties = [])
"crossorigin" => "anonymous"
]
);
// import template with options
$custom_script = ArrayData::create([
'ID' => $this->ID(),
'Options' => json_encode($this->getFieldOptions())
])->renderWith('NSWDPC/Utilities/Trumbowyg/Script');
Requirements::customScript(
$custom_script,
"trumbowyg_editor_" . $this->ID()
);

Requirements::javascript("nswdpc/silverstripe-trumbowyg:client/static/js/loader.js");

Requirements::css(
"https://cdn.jsdelivr.net/npm/trumbowyg@2.31.0/dist/ui/trumbowyg.min.css",
"screen",
Expand All @@ -115,6 +96,21 @@ public function Field($properties = [])
"crossorigin" => "anonymous"
]
);

// add any plugins
$this->addTrumbowygPluginRequirements();

// the loader script
$trumbowygLoader = <<<JAVASCRIPT
window.addEventListener(
'DOMContentLoaded',
function () {
let trumbowygLoader = new TrumbowygLoader();
trumbowygLoader.handle();
}
);
JAVASCRIPT;
Requirements::customScript($trumbowygLoader, "trumbowygLoader");
return parent::Field($properties);
}

Expand All @@ -134,11 +130,21 @@ public function Value()
public function dataValue()
{
$value = $this->value;
if (!is_string($value)) {
$value = "";
$value = is_string($value) ? trim($value) : "";

// Handle empty
if ($value === '') {
return '';
}

// Sanitise values, using the configured tagsToKeep setting
$options = $this->getFieldOptions();
$tagsToKeep = [];
if (isset($options['tagsToKeep']) && is_array($options['tagsToKeep'])) {
$tagsToKeep = $options['tagsToKeep'];
}

$this->value = ContentSanitiser::clean($value);
$this->value = ContentSanitiser::clean($value, $tagsToKeep);
return $this->value;
}

Expand Down
Loading