Silverstripe: How to allow javascript in tinymce? - tinymce

Admins and editors on one of my sites wants to include some javascript embeds like twitter, facebook, instagram or whatever, on pages. I thought it would be really easy to just set the extended_valid_elements to allow the script tag in the html editor of tinyMCE, but it apparently doesn't work.
I've added this to my config
HtmlEditorConfig::get('cms')->setOption(
'extended_valid_elements',
'script[charset|defer|language|src|type|async]'
);
And I've checked that it's passed to the ssTinyMCEconfig variable in my dom.
So far so good. But when I try to post a simple script in to my HTML-editor, it strips it. Does anyone know how this can be fixed?
Thank you!

Rather than manage custom scripts through the TinyMCE editor we could add a text variable to $db to manage the custom scripts through a plain TextareaField.
The following code works for SilverStripe 3.4:
class Page extends SiteTree {
private static $db = array(
'CustomScript' => 'Text'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.CustomScript',
TextareaField::create('CustomScript', 'Custom Script')
->setRows(30)
->addExtraClass('code')
->setAttribute('spellcheck', 'false'));
return $fields;
}
}
Here is some custom css for the CMS to make the script input fields display the text with a monospace font.
mysite/css/cms.css
.field.code textarea {
max-width: 100%;
font-family: "Courier New", Courier, monospace;
color: #000000;
background: #ffffff;
}
We enable the custom css in the cms with the following code in our config.yml file:
mysite/_config/config.yml
LeftAndMain:
extra_requirements_css:
- 'mysite/css/cms.css'
In our Page.ss template in the head tag, or at the bottom of the body tag, or whether we would like the custom script, we put the following to load our custom script:
<% if $CustomScript %>
$CustomScript.RAW
<% end_if %>
If we want site wide custom scripts we could also add this field to the Settings tab by extending our SiteConfig:
mysite/code/extensions/CustomSiteConfig.php
class CustomSiteConfig extends DataExtension {
private static $db = array(
'CustomScript' => 'Text'
);
public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab('Root.CustomScript',
TextareaField::create('CustomScript', 'Custom Script')
->setRows(30)
->addExtraClass('code')
->setAttribute('spellcheck', 'false'));
}
}
We enable this SiteConfig extension in our config.yml file:
mysite/_config/config.yml
SiteConfig:
extensions:
- CustomSiteConfig
In our Page.ss template in the head tag, or at the bottom of the body tag, we put the following to load our site wide custom script:
<% if $SiteConfig.CustomScript %>
$SiteConfig.CustomScript.RAW
<% end_if %>

I was able to get around the issue for Twitter embed by simply including the relevant script links in my header. This embedded the twitter-tweet blockquote code that I pasted into TimyMCE.

Related

TYPO3 11 content element Plain HTML with <style>

TYPO3 11.5.4
I want to add a Plain HTML content element to a page, e.g.:
<style type="text/css">
h1#testheader { font-weight: bold; }
</style>
<h1 id="testheader">
Header
</h1>
(just as a simplified example)
But rendered output is:
<style type="text/css"> h1#testheader { font-weight: bold; } </style>
Header
I thought that style is allowed because I set:
lib.parseFunc.allowTags = ...,style,...
lib.parseFunc_RTE.allowTags = ...,style,...
but it doesn't work.
Setting
lib.parseFunc.htmlSanitize = 0
helps but is there something better to allow <style> in Plain HTML content elements?
EDIT:
Looking into the DB entry the html tags are not encoded, so no "<" or ">" instead of "<" and ">", so it's not an issue of the RTE.
After some more research (hours of hours again invested) I come to the conclusion that the only way is to extend the HTMLSanitizer.
The following sources have been used for this solution:
https://punkt.de/de/blog/2021/htmlsanitizer-in-typo3.html
https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/9.5.x/Important-94484-IntroduceHTMLSanitizer.html
https://gist.github.com/ohader/2239dab247e18d23e677fd1b816f4fd5
The complete solution to allow <style>,<script> and <iframe> is shown below.
Still I am not able to allow <font> with this approach!
It's implemented in a local site package (gpcf_theme) and composer is used.
Add <style>,<script> and <iframe> to
lib.parseFunc.allowTags = ... style, ..., iframe, script, ...
in the TypoScript part of the site package.
style seems to be standard and is already part of this list.
The path to the local site package is:
local_packages/gpcf_theme
with a symbolic link to it from:
public/typo3conf/ext/gpcf_theme -> ../../../local_packages/gpcf_theme
These are the important file changes:
local_packages/gpcf_theme/composer.json:
{
"name": "oheil/gpcf_theme",
...
"autoload": {
"psr-4": {
"Oheil\\GpcfTheme\\": "Classes/"
}
},
...
}
local_packages/gpcf_theme/ext_localconf.php:
<?php
defined('TYPO3_MODE') || die();
...
$GLOBALS['TYPO3_CONF_VARS']['SYS']['htmlSanitizer']['default'] = \Oheil\GpcfTheme\MyDefaultBuilder::class;
...
local_packages/gpcf_theme/Classes/MyDefaultBuilder.php
<?php
namespace Oheil\GpcfTheme;
use TYPO3\CMS\Core\Html\DefaultSanitizerBuilder;
use TYPO3\HtmlSanitizer\Behavior;
use TYPO3\HtmlSanitizer\Behavior\Attr;
use TYPO3\HtmlSanitizer\Behavior\Tag;
class MyDefaultBuilder extends \TYPO3\CMS\Core\Html\DefaultSanitizerBuilder
{
protected function createBehavior(): \TYPO3\HtmlSanitizer\Behavior
{
// https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/9.5.x/Important-94484-IntroduceHTMLSanitizer.html
return parent::createBehavior()
->withName('common')
->withTags(
(new Tag(
'style',
Tag::ALLOW_CHILDREN + Behavior::ENCODE_INVALID_TAG
))->addAttrs(
(new Attr('type')),
...$this->globalAttrs
),
(new Tag(
'iframe',
Tag::ALLOW_CHILDREN
))->addAttrs(
...array_merge(
$this->globalAttrs,
[$this->srcAttr],
$this->createAttrs('scrolling', 'marginwidth', 'marginheight', 'frameborder', 'vspace', 'hspace', 'height', 'width')
)
),
(new Tag(
'script',
Tag::ALLOW_CHILDREN
))->addAttrs(
...array_merge(
$this->globalAttrs,
[$this->srcAttr]
)
),
// more tags...
);
}
}
After these changes of course you need to clear the TYPO3 cache and (not sure here) you need to do:
composer remove "oheil/gpcf_theme"
composer require "oheil/gpcf_theme"
The above is working now, but I am still happy for any expert to give some more insights in what is wrong or can be done better, perhaps easier?
And why does this not work for <font> ?
Example:
<font class="font713099">Some Text</font>

Where to implement XSS prevention in symfony-based REST API and Vue.js front-end

I'm building an application that requires html tags to be allowed for user comments in Vue.js.
I wan't to allow users to input a certain selection of HTML tags(p, i, ul, li) and escape/sanitize other like script or div.
Right now I see three ways of dealing with this issue:
On rendering the content with Vue.js
Before sending the response in Symfony(I'm using JMS Serializer)
Upon receiving request to the API
Personally I think that we could save the data to database with tags like script or div, and just sanitize them before sending a response.
Basically my question is where should I implement the prevention and should I allow tags like script into my database?
If you're using v-html to render the comments, then there's always the possibility of XSS. Strict HTML sanitization can mitigate the risk, but you never know.
The only surefire way to prevent XSS is to never use v-html or innerHTML. This means you'll have to parse the HTML (using DOMParser) and render the comments manually.
For something like this it will be easier if you write the render function manually so you have full control over how the comment content will be rendered – only render the HTML tags you choose. Whitelist instead of blacklist.
Don't render user-defined HTML attributes.
HTML sanitization won't be necessary on the server because the HTML will never be rendered as-is in the browser, but you can still sanitize it if you want to trim the fat beforehand.
Here's a basic example:
Vue.component('comment-content', {
functional: true,
props: {
html: {},
allowedElements: {
default: () => ['p', 'i', 'b', 'ul', 'li'],
},
},
render(h, ctx) {
const { html, allowedElements } = ctx.props;
const renderNode = node => {
switch (node.nodeType) {
case Node.TEXT_NODE: return renderTextNode(node);
case Node.ELEMENT_NODE: return renderElementNode(node);
}
};
const renderTextNode = node => {
return node.nodeValue;
};
const renderElementNode = node => {
const tag = node.tagName.toLowerCase();
if (allowedElements.includes(tag)) {
const children = [...node.childNodes].map(node => renderNode(node));
return h(tag, children);
}
};
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return [...doc.body.childNodes].map(node => renderNode(node));
},
});
new Vue({
el: '#app',
data: {
html: `
<p>Paragraph</p>
<ul>
<li>One <script>alert('Hacked')<\/script></li>
<li onmouseover="alert('Hacked')">Two</li>
<li style="color: red">Three <b>bold</b> <i>italic</i></li>
<li>Four <img src="javascript:alert('Hacked')"></li>
</ul>
<section>This element isn't allowed</section>
<p>Last paragraph</p>
`,
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<comment-content :html="html"></comment-content>
</div>

MaterialUI together with styled-components, SSR

I'm building a new project with SSR using Next.js, MaterialUI and styled-components. From what I know, MaterialUI uses JSS as a tool for SSR (according to the example in its repository). I wonder if anyone knows how I can make it work with styled-components. I opened issues in MaterialUI and styled-components repositories, both authors answered me that they don't know how to make it work together. But probably anyone did it already? Or at least can tell me where to dig to solve this problem. Thanks in advance!
You can use styled-components with material ui, but you'll end up needing to use !important a lot. Like this:
import Button from "material-ui/Button"
const MyButton = styled(Button)`
background: red !important;
`
In the project I'm working on with the same combo, I've just resorted to using the JSS style material-ui wants you to use with the whole withStyles HOC..
You may check their docs here https://material-ui.com/guides/interoperability/#styled-components, you may check the deeper elements section if you want to override specific classes https://material-ui.com/guides/interoperability/#deeper-elements
below is my example where for the switch component
const StyledSwitch = styled(({ ...other }) => (
<div>
<Switch
{...other}
classes={{ colorSecondary: 'colorSecondary', checked: 'checked', bar: 'bar' }}
/>
</div>
))`
& .colorSecondary.checked + .bar {
background-color: ${props => props.theme.lighter.toString()};
}
& .colorSecondary.checked {
color: ${props => props.theme.default.toString()};
}
`;
export default StyledSwitch;
usage
<StyledSwitch theme={lightTheme.secondary} />
this is using a theme but you can specify any color you want
Looks like we have 3 ways (could be easier, but not everything is flowers) to override Material UI styles with Styled Components. Here is my Gist.
I do it like this:
In head component of app:
const styleNode = document.createComment('insertion-point-jss')
document.head.insertBefore(styleNode, document.head.firstChild)
const generateClassName = createGenerateClassName()
const jss = create({
...jssPreset(),
insertionPoint: 'insertion-point-jss'
})
<JssProvider jss={jss} generateClassName={generateClassName}>
<Main />
</JssProvider>
and then just style:
import styled from 'styled-components'
import Select from '#material-ui/core/Select'
import Input from '#material-ui/core/Input'
import React from 'react'
export const InputM = styled(({ ...other }) => (
<Input {...other} classes={{ input: 'input' }} />
))`
color: ${p => p.theme.textColor};
& .icon {
font-family: ${p => p.theme.fontFamily};
font-size: ${p => p.theme.fontSize}px;
color: ${p => p.theme.textColor};
}
`

web.py markdown global name 'markdown' is not defined

Im trying to use markdown together with Templetor in web.py but I can't figure out what Im missing
Documentation is here http://webpy.org/docs/0.3/templetor#builtins
import markdown
t_globals = {
'datestr': web.datestr,
'markdown': markdown.markdown
}
render = web.template.render(globals=t_globals)
class Blog:
def GET(self, post_slug):
""" Render single post """
post = BlogPost.get(BlogPost.slug == post_slug)
render = web.template.render(base="layout")
return render.post({
"blogpost_title": post.title,
"blogpost_content": post.content,
"blogpost_teaser": post.teaser
})
here is how I try to use markdown inside the post.html template
$def with (values)
$var title: $values['blogpost_title']
<article class="post">
<div class="post-meta">
<h1 class="post-title">$values['blogpost_title']</h1>
</div>
<section class="post-content">
<a name="topofpage"></a>
$:markdown(values['blogpost_content'])
</section>
But Im getting this exception
type 'exceptions.NameError' at
/blog/he-ll-want-to-use-your-yacht-and-i-don-t-want-this-thing-smelling-like-fish/
global name 'markdown' is not defined
You're re-initializing render, once in global scope setting globals and once within Blog.GET setting base. Do it only once!

Tiny MCE adding custom HTML tags

I am using Tiny 4.3.3 for MODx
I need to add a
<p class="classname">
<em class="openImg"></em>
Some randome Input text by the user
<em class="closeImg"></em>
</p>
I don't mind if is an extra menu Item or is in the Paragraph dropdown menu. I just want the less time consuming work around possible.
I have tried this http://alexzag.blogspot.co.uk/2009/12/custom-tags-in-tinymce.html but somehow this doesn't work.
Could anyone point me to a good tutorial or tell me how could i add a icon or name to the drop down menu that creates the p and em tags with the right classes automatically please?
Thanks
It has been a while since the question was asked, but as i am currently making exactly the same, i thought i share my discoveries and solutions regarding this matter. :)
I am extending TinyMCE for a test-project at work and our solution needs custom tags - in some of them the user should be able to enter only one line, in others (as your em) a lot of text.
Steps to be done, in order to achieve the desired solution:
tell the TinyMCE editor, that your elements are good using the two configuration keywords extended_valid_elements and custom_elements:
tinymce.init({
selector: "textarea#editor",
// ...
extended_valid_elements : "emstart,emend",
custom_elements: "emstart,emend",
content_css: "editor.css"
});
create the two images for the opening and the closing tag. I named mine for the example emstart.png and emend.png.
create a custom CSS style for your custom elements and put them in the custom CSS file (the one that is specified in the TinyMCE configuration, in my case editor.css):
emstart {
background: url(emstart.png) no-repeat;
background-position: left -3px top -3px;
padding: 10px 10px 5px 10px;
background-color:#aabbcc;
border:1px dotted #CCCCCC;
height:50px;
width:100px;
}
emend {
background: url(emend.png) no-repeat;
background-position: left -3px bottom -3px;
padding: 5px 10px 10px 10px;
background-color:#aabbcc;
border:1px dotted #CCCCCC;
height:50px;
width:100px;
}
write a custom plugin that inputs the new tags and put it in the plugins directory. I called mine customem:
plugin code:
tinymce.PluginManager.add('customem', function(editor, url) {
// Add a button that opens a window
editor.addButton('customEmElementButton', {
text: 'Custom EM',
icon: false,
onclick: function() {
// Open window
editor.windowManager.open({
title: 'Please input text',
body: [
{type: 'textbox', name: 'description', label: 'Text'}
],
onsubmit: function(e) {
// Insert content when the window form is submitted
editor.insertContent('<emstart>EM Start</emstart><p>' + e.data.description + '</p><emend>EM End</emend>');
}
});
}
});
// Adds a menu item to the tools menu
editor.addMenuItem('customEmElementMenuItem', {
text: 'Custom EM Element',
context: 'tools',
onclick: function() {
editor.insertContent('<emstart>EM Start</emstart><p>Example text!</p><emend>EM End</emend>');
}
});
});
The last step is to load your custom plugin to the editor (using the plugin and toolbar configuration option) and enjoy the result:
tinymce.init({
selector: "textarea#editor",
height: "500px",
plugins: [
"code, preview, contextmenu, image, link, searchreplace, customem"
],
toolbar: "bold italic | example | code | preview | link | searchreplace | customEmElementButton",
contextmenu: "bold italic",
extended_valid_elements : "emstart,emend",
custom_elements: "emstart,emend",
content_css: "editor.css",
});
The editor now looks like this:
and the source like in your example:
First of all you will need to modify the tinymce setting valid_elements and valid_children to your needs (add em to the valid_elements and em as child to the tags desired (probably p) to valid_children).
Second you will need an own plugin with an own drop down or button to insert this code.
You can add one or more tag structures simply using the template plugin.
See documentation
https://www.tiny.cloud/docs/plugins/opensource/template/
See interactive example:
https://codepen.io/gpsblues/pen/WNdLgvb
tinymce.init({
selector: 'textarea#template',
height: 300,
plugins: 'template code',
menubar: 'insert',
toolbar: 'template code',
extended_valid_elements: "emstart[*],emend[*]",
templates : [
{
title: 'emstart/emend',
description: 'Add a personal tag structure with personal tags <emstart></emstart> <emend></emend>.',
content: '<p class="classname"><emstart class="openImg"></emstart>Input text<emend class="closeImg"></emend></p>'
}
],
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px}'
});