From 0ad1d00928172b1dd082b6b384c0910fe63fbddf Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 20 Jan 2022 00:33:44 -0500 Subject: [PATCH] Loosen restrictions on TV captions and descriptions Allow tags and attributes as defined in new system settings. Also, created a new stripHtml method on the php side to coordinate the rules when javascript is not applicable (e.g., when elements are created or updated programmatically). --- .../data/transport.core.system_settings.php | 36 ++++++++ core/lexicon/en/setting.inc.php | 12 +++ .../Revolution/Processors/Element/Create.php | 21 ++++- .../Revolution/Processors/Element/Update.php | 21 ++++- .../Processors/Security/Forms/Set/Update.php | 13 ++- core/src/Revolution/modTemplateVar.php | 70 ++++++++------- core/src/Revolution/modX.php | 85 +++++++++++++++++++ manager/assets/modext/util/utilities.js | 26 ++++-- .../widgets/element/modx.panel.chunk.js | 2 +- .../modext/widgets/element/modx.panel.tv.js | 31 ++++++- .../modext/widgets/fc/modx.panel.fcset.js | 9 +- manager/assets/modext/widgets/windows.js | 29 ++++++- 12 files changed, 299 insertions(+), 56 deletions(-) diff --git a/_build/data/transport.core.system_settings.php b/_build/data/transport.core.system_settings.php index cad437d613a..a9743620f87 100644 --- a/_build/data/transport.core.system_settings.php +++ b/_build/data/transport.core.system_settings.php @@ -470,6 +470,42 @@ 'area' => 'site', 'editedon' => null, ], '', true, true); +$settings['elements_caption_allowedattr'] = $xpdo->newObject(modSystemSetting::class); +$settings['elements_caption_allowedattr']->fromArray([ + 'key' => 'elements_caption_allowedattr', + 'value' => 'href', + 'xtype' => 'textfield', + 'namespace' => 'core', + 'area' => 'manager', + 'editedon' => null, +], '', true, true); +$settings['elements_caption_allowedtags'] = $xpdo->newObject(modSystemSetting::class); +$settings['elements_caption_allowedtags']->fromArray([ + 'key' => 'elements_caption_allowedtags', + 'value' => 'a', + 'xtype' => 'textfield', + 'namespace' => 'core', + 'area' => 'manager', + 'editedon' => null, +], '', true, true); +$settings['elements_description_allowedattr'] = $xpdo->newObject(modSystemSetting::class); +$settings['elements_description_allowedattr']->fromArray([ + 'key' => 'elements_description_allowedattr', + 'value' => 'href,src,class', + 'xtype' => 'textfield', + 'namespace' => 'core', + 'area' => 'manager', + 'editedon' => null, +], '', true, true); +$settings['elements_description_allowedtags'] = $xpdo->newObject(modSystemSetting::class); +$settings['elements_description_allowedtags']->fromArray([ + 'key' => 'elements_description_allowedtags', + 'value' => 'div,p,ul,ol,li,img,span,br,strong,b,em,i,a', + 'xtype' => 'textfield', + 'namespace' => 'core', + 'area' => 'manager', + 'editedon' => null, +], '', true, true); $settings['emailsender'] = $xpdo->newObject(modSystemSetting::class); $settings['emailsender']->fromArray([ 'key' => 'emailsender', diff --git a/core/lexicon/en/setting.inc.php b/core/lexicon/en/setting.inc.php index b877ea9d7c1..4d85eb44b81 100644 --- a/core/lexicon/en/setting.inc.php +++ b/core/lexicon/en/setting.inc.php @@ -240,6 +240,18 @@ $_lang['setting_default_per_page'] = 'Default Per Page'; $_lang['setting_default_per_page_desc'] = 'The default number of results to show in grids throughout the manager.'; +$_lang['setting_elements_caption_allowedattr'] = 'Element Captions: Allowed Attributes'; +$_lang['setting_elements_caption_allowedattr_desc'] = 'When adding an element caption, the html tag attribute(s) provided in this comma-separated list will be preserved. This currently only applies to template variables (TVs).'; + +$_lang['setting_elements_caption_allowedtags'] = 'Element Captions: Allowed Tags'; +$_lang['setting_elements_caption_allowedtags_desc'] = 'When adding an element caption, the html tag(s) provided in this comma-separated list will be preserved. This currently only applies to template variables (TVs).'; + +$_lang['setting_elements_description_allowedattr'] = 'Element Descriptions: Allowed Attributes'; +$_lang['setting_elements_description_allowedattr_desc'] = 'When adding an element description, the html tag attribute(s) provided in this comma-separated list will be preserved.'; + +$_lang['setting_elements_description_allowedtags'] = 'Element Descriptions: Allowed Tags'; +$_lang['setting_elements_description_allowedtags_desc'] = 'When adding an element description, the html tag(s) provided in this comma-separated list will be preserved.'; + $_lang['setting_emailsender'] = 'Registration Email From Address'; $_lang['setting_emailsender_desc'] = 'Here you can specify the email address used when sending Users their usernames and passwords.'; $_lang['setting_emailsender_err'] = 'Please state the administration email address.'; diff --git a/core/src/Revolution/Processors/Element/Create.php b/core/src/Revolution/Processors/Element/Create.php index f188a407420..457f01607e3 100644 --- a/core/src/Revolution/Processors/Element/Create.php +++ b/core/src/Revolution/Processors/Element/Create.php @@ -64,13 +64,26 @@ public function beforeSave() $elementClassName = array_pop(explode('\\', $this->classKey)); if ($elementClassName === 'modTemplateVar') { - if ($caption = $this->getProperty('caption', '')) { - $this->object->set('caption', strip_tags($caption)); + if ($caption = trim($this->getProperty('caption', ''))) { + $caption = $this->modx->stripHtml( + $caption, + $this->modx->getOption('elements_caption_allowedtags'), + $this->modx->getOption('elements_caption_allowedattr') + ); + $this->object->set('caption', $caption); } } - if ($description = $this->getProperty('description', '')) { - $this->object->set('description', strip_tags($description)); + if ($description = trim($this->getProperty('description', ''))) { + $description = $elementClassName === 'modTemplateVar' + ? $this->modx->stripHtml( + $description, + $this->modx->getOption('elements_description_allowedtags'), + $this->modx->getOption('elements_description_allowedattr') + ) + : strip_tags($description) + ; + $this->object->set('description', $description); } $name = $this->getProperty($this->elementNameField, ''); diff --git a/core/src/Revolution/Processors/Element/Update.php b/core/src/Revolution/Processors/Element/Update.php index 075e0923280..f570e487721 100644 --- a/core/src/Revolution/Processors/Element/Update.php +++ b/core/src/Revolution/Processors/Element/Update.php @@ -59,13 +59,26 @@ public function beforeSave() $elementClassName = array_pop(explode('\\', $this->classKey)); if ($elementClassName === 'modTemplateVar') { - if ($caption = $this->getProperty('caption', '')) { - $this->object->set('caption', strip_tags($caption)); + if ($caption = trim($this->getProperty('caption', ''))) { + $caption = $this->modx->stripHtml( + $caption, + $this->modx->getOption('elements_caption_allowedtags'), + $this->modx->getOption('elements_caption_allowedattr') + ); + $this->object->set('caption', $caption); } } - if ($description = $this->getProperty('description', '')) { - $this->object->set('description', strip_tags($description)); + if ($description = trim($this->getProperty('description', ''))) { + $description = $elementClassName === 'modTemplateVar' + ? $this->modx->stripHtml( + $description, + $this->modx->getOption('elements_description_allowedtags'), + $this->modx->getOption('elements_description_allowedattr') + ) + : strip_tags($description) + ; + $this->object->set('description', $description); } /* verify element has a name and that name does not already exist */ diff --git a/core/src/Revolution/Processors/Security/Forms/Set/Update.php b/core/src/Revolution/Processors/Security/Forms/Set/Update.php index 88f67b6aabb..bfdbeaa2372 100644 --- a/core/src/Revolution/Processors/Security/Forms/Set/Update.php +++ b/core/src/Revolution/Processors/Security/Forms/Set/Update.php @@ -1,4 +1,5 @@ newRules[] = $rule; } if (!empty($field['label'])) { - $field['label'] = strip_tags($field['label']); + $field['label'] = $this->modx->stripHtml( + $field['label'], + $this->modx->getOption('elements_caption_allowedtags'), + $this->modx->getOption('elements_caption_allowedattr') + ); $rule = $this->modx->newObject(modActionDom::class); $rule->set('set', $this->object->get('id')); $rule->set('action', $this->object->get('action')); @@ -287,7 +292,11 @@ public function setTVRules() $this->newRules[] = $rule; } if (!empty($tvData['label'])) { - $tvData['label'] = strip_tags($tvData['label']); + $tvData['label'] = $this->modx->stripHtml( + $tvData['label'], + $this->modx->getOption('elements_caption_allowedtags'), + $this->modx->getOption('elements_caption_allowedattr') + ); $rule = $this->modx->newObject(modActionDom::class); $rule->set('set', $this->object->get('id')); $rule->set('action', $this->object->get('action')); diff --git a/core/src/Revolution/modTemplateVar.php b/core/src/Revolution/modTemplateVar.php index d19ca95e85e..1984a1ea500 100644 --- a/core/src/Revolution/modTemplateVar.php +++ b/core/src/Revolution/modTemplateVar.php @@ -75,7 +75,7 @@ class modTemplateVar extends modElement * * {@inheritdoc} */ - function __construct(& $xpdo) + public function __construct(&$xpdo) { parent:: __construct($xpdo); $this->setToken('*'); @@ -161,7 +161,7 @@ public function process($properties = null, $content = null) /* copy the content source to the output buffer */ $this->_output = $this->_content; - if (is_string($this->_output) && !empty ($this->_output)) { + if (is_string($this->_output) && !empty($this->_output)) { /* turn the processed properties into placeholders */ $scope = $this->xpdo->toPlaceholders($this->_properties, '', '.', true); @@ -217,10 +217,10 @@ public function getValue($resourceId = 0) $value = null; $resourceId = intval($resourceId); if ($resourceId) { - if (is_object($this->xpdo->resource) && $resourceId === (integer)$this->xpdo->resourceIdentifier && is_array($this->xpdo->resource->get($this->get('name')))) { + if (is_object($this->xpdo->resource) && $resourceId === (int)$this->xpdo->resourceIdentifier && is_array($this->xpdo->resource->get($this->get('name')))) { $valueArray = $this->xpdo->resource->get($this->get('name')); $value = $valueArray[1]; - } elseif ($resourceId === (integer)$this->get('resourceId') && array_key_exists('value', $this->_fields)) { + } elseif ($resourceId === (int)$this->get('resourceId') && array_key_exists('value', $this->_fields)) { $value = $this->get('value'); } else { $resource = $this->xpdo->getObject(modTemplateVarResource::class, [ @@ -269,8 +269,7 @@ public function setValue($resourceId = 0, $value = null) $templateVarResource->set('value', $value); } $this->addMany($templateVarResource); - } elseif (!$templateVarResource->isNew() - && ($value === null || $value === $this->get('default_text'))) { + } elseif (!$templateVarResource->isNew() && ($value === null || $value === $this->get('default_text'))) { $templateVarResource->remove(); } } @@ -325,8 +324,10 @@ public function prepareOutput($value, $resourceId = 0) $mTypes = $this->xpdo->getOption('manipulatable_url_tv_output_types', null, 'image,file'); $mTypes = explode(',', $mTypes); if (!empty($value) && in_array($this->get('type'), $mTypes)) { - $context = !empty($resourceId) ? $this->xpdo->getObject(modResource::class, - $resourceId)->get('context_key') : $this->xpdo->context->get('key'); + $context = !empty($resourceId) + ? $this->xpdo->getObject(modResource::class, $resourceId)->get('context_key') + : $this->xpdo->context->get('key') + ; $sourceCache = $this->getSourceCache($context); $classKey = $sourceCache['class_key']; if (!empty($sourceCache) && !empty($classKey)) { @@ -336,8 +337,7 @@ public function prepareOutput($value, $resourceId = 0) if ($source) { $source->fromArray($sourceCache, '', true, true); $source->initialize(); - $isAbsolute = strpos($value, 'http://') === 0 || strpos($value, - 'https://') === 0 || strpos($value, 'ftp://') === 0; + $isAbsolute = strpos($value, 'http://') === 0 || strpos($value, 'https://') === 0 || strpos($value, 'ftp://') === 0; if (!$isAbsolute) { $value = $source->prepareOutputUrl($value); } @@ -380,8 +380,11 @@ public function renderInput($resource = null, $options = []) } if (!isset($this->xpdo->smarty)) { $this->xpdo->getService('smarty', modSmarty::class, '', [ - 'template_dir' => $this->xpdo->getOption('manager_path') . 'templates/' . $this->xpdo->getOption('manager_theme', - null, 'default') . '/', + 'template_dir' => $this->xpdo->getOption('manager_path') . 'templates/' . $this->xpdo->getOption( + 'manager_theme', + null, + 'default' + ) . '/' ]); } $this->xpdo->smarty->assign('style', $style); @@ -402,8 +405,12 @@ public function renderInput($resource = null, $options = []) $this->set('processedValue', $value); $this->set('default_text', $this->processBindings($this->get('default_text'), $resourceId)); - /* strip tags from description */ - // $this->set('description', strip_tags($this->get('description'))); + /* remove disallowed tags and attributes from description */ + $this->set('description', $this->modx->stripHtml( + $this->get('description'), + $this->modx->getOption('elements_description_allowedtags'), + $this->modx->getOption('elements_description_allowedattr') + )); $params = []; if ($paramstring = $this->get('display_params')) { @@ -627,8 +634,7 @@ public function checkForFormCustomizationRules($value, &$resource) $c = $this->xpdo->newQuery(modActionDom::class); $c->innerJoin(modFormCustomizationSet::class, 'FCSet'); $c->innerJoin(modFormCustomizationProfile::class, 'Profile', 'FCSet.profile = Profile.id'); - $c->leftJoin(modFormCustomizationProfileUserGroup::class, 'ProfileUserGroup', - 'Profile.id = ProfileUserGroup.profile'); + $c->leftJoin(modFormCustomizationProfileUserGroup::class, 'ProfileUserGroup', 'Profile.id = ProfileUserGroup.profile'); $c->leftJoin(modFormCustomizationProfile::class, 'UGProfile', 'UGProfile.id = ProfileUserGroup.profile'); $ruleFieldName = $this->xpdo->escape('rule'); $c->where([ @@ -654,8 +660,7 @@ public function checkForFormCustomizationRules($value, &$resource) ], xPDOQuery::SQL_AND, null, 2); } if (!empty($this->xpdo->request) && !empty($this->xpdo->request->action)) { - $wildAction = substr($this->xpdo->request->action, 0, - strrpos($this->xpdo->request->action, '/')) . '/*'; + $wildAction = substr($this->xpdo->request->action, 0, strrpos($this->xpdo->request->action, '/')) . '/*'; $c->where([ 'modActionDom.action:IN' => [$this->xpdo->request->action, $wildAction], ]); @@ -898,7 +903,7 @@ public function processBindings($value = '', $resourceId = 0, $preProcess = true case 'DOCUMENT': /* retrieve a document and process it's content */ if ($preProcess) { $query = $this->xpdo->newQuery(modResource::class, [ - 'id' => (integer)$param, + 'id' => (int)$param, 'deleted' => false, ]); $query->select('content'); @@ -919,8 +924,10 @@ public function processBindings($value = '', $resourceId = 0, $preProcess = true $dbtags['DBASE'] = $this->xpdo->getOption('dbname'); $dbtags['PREFIX'] = $this->xpdo->getOption('table_prefix'); foreach ($dbtags as $key => $pValue) { - if (!is_scalar($pValue)) continue; - $param = str_replace('[[+'.$key.']]', (string)$pValue, $param); + if (!is_scalar($pValue)) { + continue; + } + $param = str_replace('[[+' . $key . ']]', (string)$pValue, $param); } $stmt = $this->xpdo->query('SELECT ' . $param); if ($stmt && $stmt instanceof PDOStatement) { @@ -975,7 +982,6 @@ public function processBindings($value = '', $resourceId = 0, $preProcess = true default: $output = $value; break; - } /* support for nested bindings */ @@ -1005,11 +1011,11 @@ public function parseBinding($binding_string) $regexp2 = '/(\S+)\s+(.+)/is'; /* Split binding on second whitespace to get properties */ $properties = []; - if (preg_match($regexp2, $match[2] , $match2)) { + if (preg_match($regexp2, $match[2], $match2)) { if (isset($match2[2])) { - $props = json_decode($match2[2],true); + $props = json_decode($match2[2], true); $valid = json_last_error() === JSON_ERROR_NONE; - if ($valid && is_array($props)){ + if ($valid && is_array($props)) { $properties = $props; $match[2] = $match2[1]; } else { @@ -1041,8 +1047,10 @@ public function processInheritBinding($default = '', $resourceId = null) $output = $default; /* Default to param value if no content from parents */ $resource = null; $resourceColumns = $this->xpdo->getSelectColumns(modResource::class, '', '', ['id', 'parent']); - $resourceQuery = new xPDOCriteria($this->xpdo, - "SELECT {$resourceColumns} FROM {$this->xpdo->getTableName(modResource::class)} WHERE id = ?"); + $resourceQuery = new xPDOCriteria( + $this->xpdo, + "SELECT {$resourceColumns} FROM {$this->xpdo->getTableName(modResource::class)} WHERE id = ?" + ); if (!empty($resourceId) && (!($this->xpdo->resource instanceof modResource) || $this->xpdo->resource->get('id') != $resourceId)) { if ($resourceQuery->stmt && $resourceQuery->stmt->execute([$resourceId])) { $result = $resourceQuery->stmt->fetchAll(PDO::FETCH_ASSOC); @@ -1115,11 +1123,11 @@ public function findPolicy($context = '') $policy = []; $context = !empty($context) ? $context : $this->xpdo->context->get('key'); if ($context === $this->xpdo->context->get('key')) { - $catEnabled = (boolean)$this->xpdo->getOption('access_category_enabled', null, true); - $rgEnabled = (boolean)$this->xpdo->getOption('access_resource_group_enabled', null, true); + $catEnabled = (bool)$this->xpdo->getOption('access_category_enabled', null, true); + $rgEnabled = (bool)$this->xpdo->getOption('access_resource_group_enabled', null, true); } elseif ($this->xpdo->getContext($context)) { - $catEnabled = (boolean)$this->xpdo->contexts[$context]->getOption('access_category_enabled', true); - $rgEnabled = (boolean)$this->xpdo->contexts[$context]->getOption('access_resource_group_enabled', true); + $catEnabled = (bool)$this->xpdo->contexts[$context]->getOption('access_category_enabled', true); + $rgEnabled = (bool)$this->xpdo->contexts[$context]->getOption('access_resource_group_enabled', true); } $enabled = ($catEnabled || $rgEnabled); if ($enabled) { diff --git a/core/src/Revolution/modX.php b/core/src/Revolution/modX.php index 242211bba5c..22a5b4494ee 100644 --- a/core/src/Revolution/modX.php +++ b/core/src/Revolution/modX.php @@ -2977,4 +2977,89 @@ public function _postProcess() { } $this->invokeEvent('OnWebPageComplete'); } + + /** + * Removes unwanted tags and/or tag attributes from an HTML string + * + * @param string $htmlSource The html string to clean + * @param string|array $allowedTags An array or comma-separated list of tag names to allow + * @param string|array $allowedAttr An array or comma-separated list of tag attribute names to allow + */ + public function stripHTML(string $htmlSource, $allowedTags = '', $allowedAttr = ''): string + { + libxml_use_internal_errors(true); + + $allowedTags = is_string($allowedTags) ? trim($allowedTags) : $allowedTags ; + + if (empty($allowedTags)) { + return strip_tags($htmlSource); + } + + if (!is_array($allowedTags)) { + $allowedTags = preg_replace('/[\s<>]+/', '', $allowedTags); + $allowedTags = explode(',', $allowedTags); + } else { + $allowedTags = array_map(function ($tag) { + return preg_replace('/[\s<>]+/', '', $tag); + }, $allowedTags); + } + + if (!empty($allowedAttr)) { + if (!is_array($allowedAttr)) { + $allowedAttr = explode(',', $allowedAttr); + } + $allowedAttr = array_map('trim', $allowedAttr); + } else { + $allowedAttr = []; + } + + $dom = new \DOMDocument(); + + // Need a placeholder wrapping tag, as loadHTML will automatically wrap strings with no root tag with a

tag (do not want that) + $dom->loadHTML(mb_convert_encoding('' . $htmlSource . '', 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + + $xpath = new \DOMXPath($dom); + + foreach ($xpath->query("//*") as $node) { + if (in_array($node->nodeName, $allowedTags)) { + if ($node->attributes->length > 0) { + if (empty($allowedAttr)) { + for ($i = 0; $i < $node->attributes->length; $i++) { + $node->removeAttribute($node->attributes->item(0)->nodeName); + } + } else { + $nodeAttrRemove = []; + for ($i = 0; $i < $node->attributes->length; $i++) { + $name = $node->attributes->item($i)->nodeName; + if (in_array($name, $allowedAttr)) { + $testVal = preg_replace('/[^a-zA-Z:]+/', '', $node->attributes->item($i)->nodeValue); + if (stripos($testVal, 'javascript:') !== false) { + $node->attributes->item($i)->nodeValue = '#js-not-allowed#'; + } + } else { + $nodeAttrRemove[] = $name; + } + } + foreach ($nodeAttrRemove as $attr) { + $node->removeAttribute($attr); + } + } + } + } else { + $parent = $node->parentNode; + while ($node->hasChildNodes()) { + $parent->insertBefore($node->lastChild, $node->nextSibling); + } + $parent->removeChild($node); + } + } + + $output = $dom->saveHTML(); + + // The loop above should already have dropped this placeholder tag, but just in case it did not... + if (strpos($output, '') !== false) { + $output = str_replace(['', ''], '', $output); + } + return $output; + } } diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js index f17b683123a..a675cb9ba1e 100644 --- a/manager/assets/modext/util/utilities.js +++ b/manager/assets/modext/util/utilities.js @@ -139,27 +139,41 @@ Ext.reg('staticboolean',MODx.StaticBoolean); // replaces javascript invocation in a href attribute and masks html event attributes // in an input string - assuming the result is safe to be displayed by a browser MODx.util.safeHtml = function (input, allowedTags, allowedAttributes) { - var strip = function(input, allowedTags, allowedAttributes) { + + const strip = function(input, allowedTags, allowedAttributes) { return input.replace(tags, function ($0, $1) { return allowedTags.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; }).replace(attributes, function ($0, $1) { return allowedAttributes.indexOf($1.toLowerCase() + ',') > -1 ? $0 : ''; }); }; + + // Convert comma-separated list to angle-bracketed list + if (allowedTags && allowedTags.indexOf('<') === -1) { + allowedTags = allowedTags.replace(/[\s]+/g, ''); + allowedTags = `<${allowedTags.replace(/[,]+/g, '><')}>`; + } + + // Ensure allowedTags arg is a string containing only tags in lowercase () allowedTags = (((allowedTags || '
') + '') .toLowerCase() .match(/<[a-z][a-z0-9]*>/g) || []) - .join(''); // making sure the allowedTags arg is a string containing only tags in lowercase (
) + .join(''); + + // Ensure allowedAttributes arg is a comma-separated string containing only attributes in lowercase (a,b,c) allowedAttributes = (((allowedAttributes || 'href,class') + '') .toLowerCase() .match(/[a-z\-,]*/g) || []) - .join('').concat(','); // making sure the allowedAttributes arg is a comma separated string containing only attributes in lowercase (a,b,c) - var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, + .join('') + .concat(','); + + const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, attributes = /([a-z][a-z0-9]*)\s*=\s*".*?"/gi, eventAttributes = /on([a-z][a-z0-9]*\s*=)/gi, commentsAndPhpTags = /|<\?(?:php)?[\s\S]*?\?>/gi, - hrefJavascript = /href(\s*?=\s*?(["'])javascript:.*?\2|\s*?=\s*?javascript:.*?(?![^> ]))/gi, - length; + hrefJavascript = /href(\s*?=\s*?(["'])javascript:.*?\2|\s*?=\s*?javascript:.*?(?![^> ]))/gi + ; + let length; input = input.replace(commentsAndPhpTags, '').replace(hrefJavascript, 'href="javascript:void(0)"'); do { length = input.length; diff --git a/manager/assets/modext/widgets/element/modx.panel.chunk.js b/manager/assets/modext/widgets/element/modx.panel.chunk.js index 288ba684494..ea8c4d1a892 100644 --- a/manager/assets/modext/widgets/element/modx.panel.chunk.js +++ b/manager/assets/modext/widgets/element/modx.panel.chunk.js @@ -133,7 +133,7 @@ MODx.panel.Chunk = function(config) { ,value: config.record.category || 0 ,listeners: { 'afterrender': {scope:this,fn:function(f,e) { - MODx.setStaticElementPath('chunk'); + MODx.setStaticElementPath('chunk'); }} ,'select': {scope:this,fn:function(f,e) { MODx.setStaticElementPath('chunk'); diff --git a/manager/assets/modext/widgets/element/modx.panel.tv.js b/manager/assets/modext/widgets/element/modx.panel.tv.js index f5b8510f67b..6b3e163806a 100644 --- a/manager/assets/modext/widgets/element/modx.panel.tv.js +++ b/manager/assets/modext/widgets/element/modx.panel.tv.js @@ -109,7 +109,12 @@ MODx.panel.TV = function(config) { ,scope: this }, change: { - fn: MODx.util.stripAndEncode.onChange + fn: function(cmp) { + const value = cmp.getValue().trim(); + if (value.length > 0) { + cmp.setValue(Ext.util.Format.stripTags(value)); + } + } }, blur: { fn: function(cmp) { @@ -149,7 +154,7 @@ MODx.panel.TV = function(config) { ,tabIndex: 2 ,listeners: { afterrender: {scope:this,fn:function(f,e) { - MODx.setStaticElementPath('tv'); + MODx.setStaticElementPath('tv'); }} ,select: {scope:this,fn:function(f,e) { MODx.setStaticElementPath('tv'); @@ -193,7 +198,16 @@ MODx.panel.TV = function(config) { ,value: config.record.caption ,listeners: { change: { - fn: MODx.util.stripAndEncode.onChange + fn: function(cmp) { + const value = cmp.getValue().trim(); + if (value.length > 0) { + cmp.setValue(MODx.util.safeHtml( + value, + MODx.config.elements_caption_allowedtags, + MODx.config.elements_caption_allowedattr + )); + } + } } } },{ @@ -260,7 +274,16 @@ MODx.panel.TV = function(config) { ,value: config.record.description || '' ,listeners: { change: { - fn: MODx.util.stripAndEncode.onChange + fn: function(cmp) { + const value = cmp.getValue().trim(); + if (value.length > 0) { + cmp.setValue(MODx.util.safeHtml( + value, + MODx.config.elements_description_allowedtags, + MODx.config.elements_description_allowedattr + )); + } + } } } },{ diff --git a/manager/assets/modext/widgets/fc/modx.panel.fcset.js b/manager/assets/modext/widgets/fc/modx.panel.fcset.js index 771fd9a1269..f15bb8be46b 100644 --- a/manager/assets/modext/widgets/fc/modx.panel.fcset.js +++ b/manager/assets/modext/widgets/fc/modx.panel.fcset.js @@ -475,7 +475,14 @@ MODx.grid.FCSetTVs = function(config) { this.propRecord = Ext.data.Record.create(config.fields); this.on('afteredit', function(e) { if (e.field === 'label') { - const value = MODx.util.stripAndEncode.run(e.value); + let value = e.value.trim(); + if (value.length > 0) { + value = MODx.util.safeHtml( + value, + MODx.config.elements_caption_allowedtags, + MODx.config.elements_caption_allowedattr + ) + } e.record.set('label', value); e.record.commit(); } diff --git a/manager/assets/modext/widgets/windows.js b/manager/assets/modext/widgets/windows.js index d1054f9a6a3..77f80598d95 100644 --- a/manager/assets/modext/widgets/windows.js +++ b/manager/assets/modext/widgets/windows.js @@ -806,7 +806,12 @@ MODx.window.QuickCreateTV = function(config) { ,blankText: _('tv_err_ns_name') ,listeners: { change: { - fn: MODx.util.stripAndEncode.onChange + fn: function(cmp) { + const value = cmp.getValue().trim(); + if (value.length > 0) { + cmp.setValue(Ext.util.Format.stripTags(value)); + } + } } } },{ @@ -817,7 +822,16 @@ MODx.window.QuickCreateTV = function(config) { ,anchor: '100%' ,listeners: { change: { - fn: MODx.util.stripAndEncode.onChange + fn: function(cmp) { + const value = cmp.getValue().trim(); + if (value.length > 0) { + cmp.setValue(MODx.util.safeHtml( + value, + MODx.config.elements_caption_allowedtags, + MODx.config.elements_caption_allowedattr + )); + } + } } } },{ @@ -837,7 +851,16 @@ MODx.window.QuickCreateTV = function(config) { ,anchor: '100%' ,listeners: { change: { - fn: MODx.util.stripAndEncode.onChange + fn: function(cmp) { + const value = cmp.getValue().trim(); + if (value.length > 0) { + cmp.setValue(MODx.util.safeHtml( + value, + MODx.config.elements_description_allowedtags, + MODx.config.elements_description_allowedattr + )); + } + } } } }]