diff --git a/app/src/Controller/AppController.php b/app/src/Controller/AppController.php index 7a6dc4d4e..0ea3cec5d 100644 --- a/app/src/Controller/AppController.php +++ b/app/src/Controller/AppController.php @@ -44,6 +44,8 @@ use Cake\ORM\TableRegistry; use Cake\Routing\Router; use Cake\Utility\Hash; +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; /** * @property \App\Controller\Component\RegistryAuthComponent $RegistryAuth @@ -692,8 +694,26 @@ protected function getTheme() { // because we don't necessarily know what information a model-specific Theme // is based on. - if(method_exists($this, "getSpecificTheme")) { - $theme = $this->getSpecificTheme(); + if(method_exists($this, 'getSpecificTheme')) { + $specificTheme = $this->getSpecificTheme(); + + // Even if the method exists, be sure it doesn't return null. + if(isset($specificTheme)) { + $theme = $specificTheme; + } + } + + if(isset($theme)) { + $htmlSanitizer = new HtmlSanitizer( + // Allow all elements from the W3C Sanitizer API. This is more permissive than "allowSafeElements()". + // See: https://github.com/symfony/symfony/blob/7.2/src/Symfony/Component/HtmlSanitizer/Reference/W3CReference.php + (new HtmlSanitizerConfig())->allowStaticElements() + ); + + $theme->set([ + 'sanitized_header' => $htmlSanitizer->sanitize($theme->header), + 'sanitized_footer' => $htmlSanitizer->sanitize($theme->footer) + ]); } $this->set('vv_theme', $theme); diff --git a/app/src/Lib/Traits/ValidationTrait.php b/app/src/Lib/Traits/ValidationTrait.php index 9bc7836f4..bf046dc13 100644 --- a/app/src/Lib/Traits/ValidationTrait.php +++ b/app/src/Lib/Traits/ValidationTrait.php @@ -63,6 +63,33 @@ public function registerClonableValidation( return $validator; } + /** + * Register validation rules for the provided field, as HTML. + * + * @since COmanage Registry v5.0.0 + * @param Validator $validator Cake Validator + * @param TableSchemaInterface $schema Cake Schema + * @param string $field Field name + * @return Validator Cake Validator + */ + + public function registerHtmlValidation( + Validator $validator, + TableSchemaInterface $schema, + string $field + ): Validator { + $validator->add($field, [ + 'filter' => ['rule' => ['validateInput', ['type' => 'html']], + 'provider' => 'table'], + 'size' => ['rule' => ['validateMaxLength', ['column' => $schema->getColumn($field)]], + 'provider' => 'table'] + ]); + + $validator->allowEmptyString($field); + + return $validator; + } + /** * Register validation rules for the primary link key(s) associated with this table. * diff --git a/app/src/Model/Table/MessageTemplatesTable.php b/app/src/Model/Table/MessageTemplatesTable.php index 384929fa7..1ee75c2bf 100644 --- a/app/src/Model/Table/MessageTemplatesTable.php +++ b/app/src/Model/Table/MessageTemplatesTable.php @@ -150,11 +150,7 @@ public function validationDefault(Validator $validator): Validator { ]); $validator->allowEmptyString('body_text'); - $validator->add('body_html', [ - 'filter' => ['rule' => ['validateInput',['type' => 'html']], - 'provider' => 'table'] - ]); - $validator->allowEmptyString('body_html'); + $this->registerHtmlValidation($validator, $schema, 'body_html'); $this->registerStringValidation($validator, $schema, 'cc', false); diff --git a/app/src/Model/Table/MostlyStaticPagesTable.php b/app/src/Model/Table/MostlyStaticPagesTable.php index 76d1d9a0a..3f80f8545 100644 --- a/app/src/Model/Table/MostlyStaticPagesTable.php +++ b/app/src/Model/Table/MostlyStaticPagesTable.php @@ -273,11 +273,7 @@ public function validationDefault(Validator $validator): Validator { ]); $validator->notEmptyString('context'); - $validator->add('body', [ - 'filter' => ['rule' => ['validateInput', ['type' => 'html']], - 'provider' => 'table'] - ]); - $validator->allowEmptyString('body'); + $this->registerHtmlValidation($validator, $schema, 'body'); $validator->add('theme_id', [ 'content' => ['rule' => 'isInteger'] diff --git a/app/src/Model/Table/ThemesTable.php b/app/src/Model/Table/ThemesTable.php index bb1b5badd..643a1fbf8 100644 --- a/app/src/Model/Table/ThemesTable.php +++ b/app/src/Model/Table/ThemesTable.php @@ -68,6 +68,8 @@ public function initialize(array $config): void { $this->setPrimaryLink('co_id'); $this->setRequiresCO(true); + // Return to the form on save to make theme development easier. + $this->setRedirectGoal('self'); $this->setPermissions([ // Actions that operate over an entity (ie: require an $id) @@ -114,9 +116,9 @@ public function validationDefault(Validator $validator): Validator { $this->registerStringValidation($validator, $schema, 'css', false); - $this->registerStringValidation($validator, $schema, 'header', false); + $this->registerHtmlValidation($validator, $schema, 'header'); - $this->registerStringValidation($validator, $schema, 'footer', false); + $this->registerHtmlValidation($validator, $schema, 'footer'); return $validator; } diff --git a/app/templates/MessageTemplates/fields.inc b/app/templates/MessageTemplates/fields.inc index be05fef67..b429640a5 100644 --- a/app/templates/MessageTemplates/fields.inc +++ b/app/templates/MessageTemplates/fields.inc @@ -36,7 +36,7 @@ $fields = [ ], 'body_html' => [ 'afterField' => - '