diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 555d59a14..cfb29d1e4 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -8130,6 +8130,25 @@ class AttributeImage extends AttributeBlob return "Image"; } + /** + * {@inheritDoc} + * @see AttributeBlob::MakeRealValue() + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + $oDoc = parent::MakeRealValue($proposedValue, $oHostObj); + + if (($oDoc instanceof ormDocument) + && (false === $oDoc->IsEmpty()) + && ($oDoc->GetMimeType() === 'image/svg+xml')) { + $sCleanSvg = HTMLSanitizer::Sanitize($oDoc->GetData(), 'svg_sanitizer'); + $oDoc = new ormDocument($sCleanSvg, $oDoc->GetMimeType(), $oDoc->GetFileName()); + } + + // The validation of the MIME Type is done by CheckFormat below + return $oDoc; + } + /** * Check that the supplied ormDocument actually contains an image * {@inheritDoc} diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 179fd7f3f..919ba072a 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -1125,6 +1125,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ], + 'svg_sanitizer' => [ + 'type' => 'string', + 'description' => 'The class to use for SVG sanitization : allow to provide a custom made sanitizer', + 'default' => 'SvgDOMSanitizer', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], 'inline_image_max_display_width' => [ 'type' => 'integer', 'description' => 'The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.', diff --git a/core/htmlsanitizer.class.inc.php b/core/htmlsanitizer.class.inc.php index d9a12b569..4502e06d7 100644 --- a/core/htmlsanitizer.class.inc.php +++ b/core/htmlsanitizer.class.inc.php @@ -97,64 +97,163 @@ class HTMLNullSanitizer extends HTMLSanitizer { return $sHTML; } - } + + /** - * A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang - * Complete but quite slow - * http://htmlpurifier.org + * Common implementation for sanitizer using DOM parsing */ -/* -class HTMLPurifierSanitizer extends HTMLSanitizer +abstract class DOMSanitizer extends HTMLSanitizer { - protected static $oPurifier = null; + /** @var DOMDocument */ + protected $oDoc; - public function __construct() - { - if (self::$oPurifier == null) - { - $sLibPath = APPROOT.'lib/htmlpurifier/HTMLPurifier.auto.php'; - if (!file_exists($sLibPath)) - { - throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer."); - } - require_once($sLibPath); + abstract public function GetTagsWhiteList(); - $oPurifierConfig = HTMLPurifier_Config::createDefault(); - $oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8' - $oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional' - $oPurifierConfig->set('URI.AllowedSchemes', array ( - 'http' => true, - 'https' => true, - 'data' => true, // This one is not present by default - )); - $sPurifierCache = APPROOT.'data/HTMLPurifier'; - if (!is_dir($sPurifierCache)) - { - mkdir($sPurifierCache); - } - if (!is_dir($sPurifierCache)) - { - throw new Exception("Could not create the cache directory '$sPurifierCache'"); - } - $oPurifierConfig->set('Cache.SerializerPath', $sPurifierCache); // no trailing slash - self::$oPurifier = new HTMLPurifier($oPurifierConfig); - } - } + abstract public function GetTagsBlackList(); + + abstract public function GetAttrsWhiteList(); + + abstract public function GetAttrsBlackList(); + + abstract public function GetStylesWhiteList(); public function DoSanitize($sHTML) { - $sCleanHtml = self::$oPurifier->purify($sHTML); + $this->oDoc = new DOMDocument(); + $this->oDoc->preserveWhitespace = true; + + // MS outlook implements empty lines by the mean of

+ // We have to transform that into


(which is how Thunderbird implements empty lines) + // Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace) + // therefore we have to do the transformation upfront + $sHTML = preg_replace('@(\s| )*@', '
', $sHTML); + + $this->LoadDoc($sHTML); + + $this->CleanNode($this->oDoc); + + $sCleanHtml = $this->PrintDoc(); + return $sCleanHtml; } + + abstract public function LoadDoc($sHTML); + + /** + * @return string cleaned source + * @uses \DOMSanitizer::oDoc + */ + abstract public function PrintDoc(); + + protected function CleanNode(DOMNode $oElement) + { + $aAttrToRemove = array(); + // Gather the attributes to remove + if ($oElement->hasAttributes()) { + foreach ($oElement->attributes as $oAttr) { + $sAttr = strtolower($oAttr->name); + if ((false === empty($this->GetAttrsBlackList())) + && (in_array($sAttr, $this->GetAttrsBlackList(), true))) { + $aAttrToRemove[] = $oAttr->name; + } else if ((false === empty($this->GetTagsWhiteList())) + && (false === in_array($sAttr, $this->GetTagsWhiteList()[strtolower($oElement->tagName)]))) { + $aAttrToRemove[] = $oAttr->name; + } else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value)) { + // Invalid content + $aAttrToRemove[] = $oAttr->name; + } else if ($sAttr == 'style') { + // Special processing for style tags + $sCleanStyle = $this->CleanStyle($oAttr->value); + if ($sCleanStyle == '') { + // Invalid content + $aAttrToRemove[] = $oAttr->name; + } else { + $oElement->setAttribute($oAttr->name, $sCleanStyle); + } + } + } + // Now remove them + foreach($aAttrToRemove as $sName) + { + $oElement->removeAttribute($sName); + } + } + + if ($oElement->hasChildNodes()) + { + $aChildElementsToRemove = array(); + // Gather the child noes to remove + foreach($oElement->childNodes as $oNode) { + if ($oNode instanceof DOMElement) { + $sNodeTagName = strtolower($oNode->tagName); + } + if (($oNode instanceof DOMElement) + && (false === empty($this->GetTagsBlackList())) + && (in_array($sNodeTagName, $this->GetTagsBlackList(), true))) { + $aChildElementsToRemove[] = $oNode; + } else if (($oNode instanceof DOMElement) + && (false === empty($this->GetTagsWhiteList())) + && (false === array_key_exists($sNodeTagName, $this->GetTagsWhiteList()))) { + $aChildElementsToRemove[] = $oNode; + } else if ($oNode instanceof DOMComment) { + $aChildElementsToRemove[] = $oNode; + } else { + // Recurse + $this->CleanNode($oNode); + if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img')) { + InlineImage::ProcessImageTag($oNode); + } + } + } + // Now remove them + foreach($aChildElementsToRemove as $oDomElement) + { + $oElement->removeChild($oDomElement); + } + } + } + + protected function IsValidAttributeContent($sAttributeName, $sValue) + { + if ((false === empty($this->GetAttrsBlackList())) + && (in_array($sAttributeName, $this->GetAttrsBlackList(), true))) { + return true; + } + + if (array_key_exists($sAttributeName, $this->GetAttrsWhiteList())) { + return preg_match($this->GetAttrsWhiteList()[$sAttributeName], $sValue); + } + + return true; + } + + protected function CleanStyle($sStyle) + { + if (empty($this->GetStylesWhiteList())) { + return $sStyle; + } + + $aAllowedStyles = array(); + $aItems = explode(';', $sStyle); + { + foreach ($aItems as $sItem) { + $aElements = explode(':', trim($sItem)); + if (in_array(trim(strtolower($aElements[0])), $this->GetStylesWhiteList())) { + $aAllowedStyles[] = trim($sItem); + } + } + } + + return implode(';', $aAllowedStyles); + } } -*/ -class HTMLDOMSanitizer extends HTMLSanitizer + + +class HTMLDOMSanitizer extends DOMSanitizer { - protected $oDoc; - /** * @var array * @see https://www.itophub.io/wiki/page?id=2_6_0%3Aadmin%3Arich_text_limitations @@ -239,6 +338,31 @@ class HTMLDOMSanitizer extends HTMLSanitizer 'white-space', ); + public function GetTagsWhiteList() + { + return static::$aTagsWhiteList; + } + + public function GetTagsBlackList() + { + return []; + } + + public function GetAttrsWhiteList() + { + return static::$aAttrsWhiteList; + } + + public function GetAttrsBlackList() + { + return []; + } + + public function GetStylesWhiteList() + { + return static::$aStylesWhiteList; + } + public function __construct() { parent::__construct(); @@ -264,139 +388,152 @@ class HTMLDOMSanitizer extends HTMLSanitizer } } - public function DoSanitize($sHTML) + public function LoadDoc($sHTML) { - $this->oDoc = new DOMDocument(); - $this->oDoc->preserveWhitespace = true; - - // MS outlook implements empty lines by the mean of

- // We have to transform that into


(which is how Thunderbird implements empty lines) - // Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace) - // therefore we have to do the transformation upfront - $sHTML = preg_replace('@(\s| )*@', '
', $sHTML); - // Replace badly encoded non breaking space - $sHTML = preg_replace('~\xc2\xa0~', ' ', $sHTML); - @$this->oDoc->loadHTML(''.$sHTML); // For loading HTML chunks where the character set is not specified + $this->oDoc->preserveWhitespace = true; + } - $this->CleanNode($this->oDoc); - + public function PrintDoc() + { $oXPath = new DOMXPath($this->oDoc); $sXPath = "//body"; $oNodesList = $oXPath->query($sXPath); - if ($oNodesList->length == 0) - { + if ($oNodesList->length == 0) { // No body, save the whole document $sCleanHtml = $this->oDoc->saveHTML(); - } - else - { + } else { // Export only the content of the body tag $sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0)); // remove the body tag itself - $sCleanHtml = str_replace( array('', ''), '', $sCleanHtml); + $sCleanHtml = str_replace(array('', ''), '', $sCleanHtml); } return $sCleanHtml; } +} - protected function CleanNode(DOMNode $oElement) + + +/** + * @since 2.6.5 2.7.6 3.0.0 N°4360 + */ +class SvgDOMSanitizer extends DOMSanitizer +{ + public function GetTagsWhiteList() { - $aAttrToRemove = array(); - // Gather the attributes to remove - if ($oElement->hasAttributes()) - { - foreach($oElement->attributes as $oAttr) - { - $sAttr = strtolower($oAttr->name); - if (!in_array($sAttr, self::$aTagsWhiteList[strtolower($oElement->tagName)])) - { - // Forbidden (or unknown) attribute - $aAttrToRemove[] = $oAttr->name; - } - else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value)) - { - // Invalid content - $aAttrToRemove[] = $oAttr->name; - } - else if ($sAttr == 'style') - { - // Special processing for style tags - $sCleanStyle = $this->CleanStyle($oAttr->value); - if ($sCleanStyle == '') - { - // Invalid content - $aAttrToRemove[] = $oAttr->name; - } - else - { - $oElement->setAttribute($oAttr->name, $sCleanStyle); - } - } - } - // Now remove them - foreach($aAttrToRemove as $sName) - { - $oElement->removeAttribute($sName); - } - } - - if ($oElement->hasChildNodes()) - { - $aChildElementsToRemove = array(); - // Gather the child noes to remove - foreach($oElement->childNodes as $oNode) - { - if (($oNode instanceof DOMElement) && (!array_key_exists(strtolower($oNode->tagName), self::$aTagsWhiteList))) - { - $aChildElementsToRemove[] = $oNode; - } - else if ($oNode instanceof DOMComment) - { - $aChildElementsToRemove[] = $oNode; - } - else - { - // Recurse - $this->CleanNode($oNode); - if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img')) - { - InlineImage::ProcessImageTag($oNode); - } - } - } - // Now remove them - foreach($aChildElementsToRemove as $oDomElement) - { - $oElement->removeChild($oDomElement); - } - } + return []; } - protected function CleanStyle($sStyle) + /** + * @return string[] + * @link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script + */ + public function GetTagsBlackList() { - $aAllowedStyles = array(); - $aItems = explode(';', $sStyle); - { - foreach($aItems as $sItem) - { - $aElements = explode(':', trim($sItem)); - if (in_array(trim(strtolower($aElements[0])), static::$aStylesWhiteList)) - { - $aAllowedStyles[] = trim($sItem); - } - } - } - return implode(';', $aAllowedStyles); + return [ + 'script', + ]; } - protected function IsValidAttributeContent($sAttributeName, $sValue) + public function GetAttrsWhiteList() { - if (array_key_exists($sAttributeName, self::$aAttrsWhiteList)) - { - return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue); - } - return true; + return []; + } + + /** + * @return string[] + * @link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Events#document_event_attributes + */ + public function GetAttrsBlackList() + { + return [ + 'onbegin', + 'onbegin', + 'onrepeat', + 'onabort', + 'onerror', + 'onerror', + 'onscroll', + 'onunload', + 'oncopy', + 'oncut', + 'onpaste', + 'oncancel', + 'oncanplay', + 'oncanplaythrough', + 'onchange', + 'onclick', + 'onclose', + 'oncuechange', + 'ondblclick', + 'ondrag', + 'ondragend', + 'ondragenter', + 'ondragleave', + 'ondragover', + 'ondragstart', + 'ondrop', + 'ondurationchange', + 'onemptied', + 'onended', + 'onerror', + 'onfocus', + 'oninput', + 'oninvalid', + 'onkeydown', + 'onkeypress', + 'onkeyup', + 'onload', + 'onloadeddata', + 'onloadedmetadata', + 'onloadstart', + 'onmousedown', + 'onmouseenter', + 'onmouseleave', + 'onmousemove', + 'onmouseout', + 'onmouseover', + 'onmouseup', + 'onmousewheel', + 'onpause', + 'onplay', + 'onplaying', + 'onprogress', + 'onratechange', + 'onreset', + 'onresize', + 'onscroll', + 'onseeked', + 'onseeking', + 'onselect', + 'onshow', + 'onstalled', + 'onsubmit', + 'onsuspend', + 'ontimeupdate', + 'ontoggle', + 'onvolumechange', + 'onwaiting', + 'onactivate', + 'onfocusin', + 'onfocusout', + ]; + } + + public function GetStylesWhiteList() + { + return []; + } + + public function LoadDoc($sHTML) + { + @$this->oDoc->loadXml($sHTML, LIBXML_NOBLANKS); + } + + public function PrintDoc() + { + return $this->oDoc->saveXML(); } } diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php index d0c151a48..7122cf07c 100644 --- a/core/ormtagset.class.inc.php +++ b/core/ormtagset.class.inc.php @@ -138,7 +138,7 @@ final class ormTagSet extends ormSet } /** - * @return array of tags indexed by code + * @return array index: code, value: corresponding {@see \TagSetFieldData} */ public function GetTags() { diff --git a/sources/Renderer/Bootstrap/FieldRenderer/BsSetFieldRenderer.php b/sources/Renderer/Bootstrap/FieldRenderer/BsSetFieldRenderer.php index 0d39d3a3b..85530890f 100644 --- a/sources/Renderer/Bootstrap/FieldRenderer/BsSetFieldRenderer.php +++ b/sources/Renderer/Bootstrap/FieldRenderer/BsSetFieldRenderer.php @@ -107,18 +107,36 @@ EOF // ... in view mode else { - $aItems = $oOrmItemSet->GetTags(); + if ($oOrmItemSet instanceof \ormTagSet) { + $aItems = $oOrmItemSet->GetTags(); + $fExtractTagData = static function($oTag, &$sItemLabel, &$sItemDescription) { + $sItemLabel = $oTag->Get('label'); + $sItemDescription = $oTag->Get('description'); + }; + } else { + $aItems = $oOrmItemSet->GetValues(); + $oAttDef = MetaModel::GetAttributeDef($oOrmItemSet->GetClass(), $oOrmItemSet->GetAttCode()); + $fExtractTagData = static function($sEnumSetValue, &$sItemLabel, &$sItemDescription) use ($oAttDef) { + $sItemLabel = $oAttDef->GetValueLabel($sEnumSetValue); + $sItemDescription = ''; + }; + } + $oOutput->AddHtml('
') ->AddHtml(''); - foreach($aItems as $sItemCode => $oItem) + foreach($aItems as $sItemCode => $value) { - $sItemLabel = $oItem->Get('label'); - $sItemDescription = $oItem->Get('description'); + $fExtractTagData($value, $sItemLabel, $sItemDescription); + + $sDescriptionAttr = (empty($sItemDescription)) + ? '' + : ' data-description="'.utils::HtmlEntities($sItemDescription).'"'; + $oOutput->AddHtml('') + ->AddHtml('"') + ->AddHtml($sDescriptionAttr) + ->AddHtml('>') ->AddHtml($sItemLabel, true) ->AddHtml(''); } diff --git a/sources/Renderer/RenderingOutput.php b/sources/Renderer/RenderingOutput.php index 4350dfa7a..e76e290a3 100644 --- a/sources/Renderer/RenderingOutput.php +++ b/sources/Renderer/RenderingOutput.php @@ -20,6 +20,8 @@ namespace Combodo\iTop\Renderer; +use utils; + /** * Description of RenderingOutput * @@ -111,15 +113,15 @@ class RenderingOutput /** * - * @param string $sHtml - * @param bool $bEncodeHtmlEntities + * @param ?string $sHtml + * @param bool $bEscapeHtmlEntities * * @return \Combodo\iTop\Renderer\RenderingOutput */ - public function AddHtml(?string $sHtml, bool $bEncodeHtmlEntities = false) + public function AddHtml(?string $sHtml, bool $bEscapeHtmlEntities = false) { if (!is_null($sHtml)) { - $this->sHtml .= ($bEncodeHtmlEntities) ? htmlentities($sHtml, ENT_QUOTES, 'UTF-8') : $sHtml; + $this->sHtml .= ($bEscapeHtmlEntities) ? utils::Escapehtml($sHtml) : $sHtml; } return $this; diff --git a/test/core/sanitizer/AbstractDOMSanitizerTest.php b/test/core/sanitizer/AbstractDOMSanitizerTest.php new file mode 100644 index 000000000..469de3f2a --- /dev/null +++ b/test/core/sanitizer/AbstractDOMSanitizerTest.php @@ -0,0 +1,75 @@ +assertEquals($sOutputHtml, $sRes); } - private function ReadTestFile($sFileToTest, $sFolderName) - { - $sCurrentPath = __DIR__; - - return file_get_contents($sCurrentPath.DIRECTORY_SEPARATOR - .$sFolderName.DIRECTORY_SEPARATOR - .$sFileToTest); - } - - private function RemoveNewLines($sText) - { - $sText = str_replace("\r\n", "\n", $sText); - $sText = str_replace("\r", "\n", $sText); - $sText = str_replace("\n", '', $sText); - - return $sText; - } - public function DoSanitizeProvider() { return array( array( - 'utf-8_wrong_character_email_truncated.txt', + 'scripts.html', ), ); } - /** * @dataProvider WhiteListProvider * @@ -146,13 +126,11 @@ class HTMLDOMSanitizerTest extends ItopTestCase $aTestCaseArray = array(); $sInputText = $this->ReadTestFile('whitelist_test.html', self::INPUT_DIRECTORY); - foreach ($aTagsWhiteList as $sTag => $aTagAttributes) - { + foreach ($aTagsWhiteList as $sTag => $aTagAttributes) { $sTestCaseText = $sInputText; $sStartTag = "<$sTag"; $iAttrCounter = 0; - foreach ($aTagAttributes as $sTagAttribute) - { + foreach ($aTagAttributes as $sTagAttribute) { $sStartTag .= $this->GetTagAttributeValue($sTagAttribute, $iAttrCounter); $iAttrCounter++; } @@ -168,41 +146,6 @@ class HTMLDOMSanitizerTest extends ItopTestCase return $aTestCaseArray; } - /** - * Generates an appropriate value for the given attribute, or use the counter if needed. - * This is necessary as most of the attributes with empty or inappropriate values (like a numeric for a href) are removed by the parser - * - * @param string $sTagAttribute - * @param int $iAttributeCounter - * - * @return string attribute value - */ - private function GetTagAttributeValue($sTagAttribute, $iAttributeCounter) - { - $sTagAttrValue = ' '.$sTagAttribute.'="'; - if (in_array($sTagAttribute, array('href', 'src'))) - { - return $sTagAttrValue.'http://www.combodo.com"'; - } - - if ($sTagAttribute === 'style') - { - return $sTagAttrValue.'color: black"'; - } - - return $sTagAttrValue.$iAttributeCounter.'"'; - } - - private function IsClosingTag($sTag) - { - if (in_array($sTag, array('br', 'img', 'hr'))) - { - return false; - } - - return true; - } - /** * @dataProvider RemoveBlackListedTagContentProvider */ diff --git a/test/core/sanitizer/SvgDOMSanitizerTest.php b/test/core/sanitizer/SvgDOMSanitizerTest.php new file mode 100644 index 000000000..b45a95a39 --- /dev/null +++ b/test/core/sanitizer/SvgDOMSanitizerTest.php @@ -0,0 +1,53 @@ +ReadTestFile($sFileToTest, self::INPUT_DIRECTORY); + $sOutputHtml = $this->ReadTestFile($sFileToTest, self::OUTPUT_DIRECTORY); + $sOutputHtml = $this->RemoveNewLines($sOutputHtml); + + $oSanitizer = new SvgDOMSanitizer(); + $sRes = $oSanitizer->DoSanitize($sInputHtml); + + // Removing newlines as the parser gives different results depending on the PHP version + // Didn't manage to get it right : + // - no php.ini difference + // - playing with the parser preserveWhitespace/formatOutput parser options didn't help + // So we're removing new lines on both sides :/ + $sOutputHtml = $this->RemoveNewLines($sOutputHtml); + $sRes = $this->RemoveNewLines($sRes); + + $this->debug($sRes); + $this->assertEquals($sOutputHtml, $sRes); + } + + public function DoSanitizeProvider() + { + return array( + array( + 'scripts.svg', + ), + ); + } +} + diff --git a/test/core/sanitizer/input/scripts.html b/test/core/sanitizer/input/scripts.html new file mode 100644 index 000000000..5dceaecae --- /dev/null +++ b/test/core/sanitizer/input/scripts.html @@ -0,0 +1,7 @@ +

Test with lots of JS scripts to filter !

+ +

+ + diff --git a/test/core/sanitizer/input/scripts.svg b/test/core/sanitizer/input/scripts.svg new file mode 100644 index 000000000..eba8dbd7d --- /dev/null +++ b/test/core/sanitizer/input/scripts.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/test/core/sanitizer/output/scripts.html b/test/core/sanitizer/output/scripts.html new file mode 100644 index 000000000..ac0043227 --- /dev/null +++ b/test/core/sanitizer/output/scripts.html @@ -0,0 +1,3 @@ +

Test with lots of JS scripts to filter !

+ +

diff --git a/test/core/sanitizer/output/scripts.svg b/test/core/sanitizer/output/scripts.svg new file mode 100644 index 000000000..41508b547 --- /dev/null +++ b/test/core/sanitizer/output/scripts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/integration/DictionariesConsistencyTest.php b/test/integration/DictionariesConsistencyTest.php index 8a851b776..fbc8ef031 100644 --- a/test/integration/DictionariesConsistencyTest.php +++ b/test/integration/DictionariesConsistencyTest.php @@ -34,7 +34,10 @@ class DictionariesConsistencyTest extends ItopTestCase 'da' => array('DA DA', 'Danish', 'Dansk'), 'de' => array('DE DE', 'German', 'Deutsch'), 'en' => array('EN US', 'English', 'English'), - 'es_cr' => array('ES CR', 'Spanish', 'Español, Castellano'), + 'es_cr' => array('ES CR', 'Spanish', array( + 'Español, Castellaño', // old value + 'Español, Castellano', // new value since N°3635 + )), 'fr' => array('FR FR', 'French', 'Français'), 'hu' => array('HU HU', 'Hungarian', 'Magyar'), 'it' => array('IT IT', 'Italian', 'Italiano'), @@ -57,7 +60,7 @@ class DictionariesConsistencyTest extends ItopTestCase static::fail("Unknown prefix '$sLangPrefix' for dictionary file '$sDictFile'"); } - [$sExpectedLanguageCode, $sExpectedEnglishLanguageDesc, $sExpectedLocalizedLanguageDesc] = $aPrefixToLanguageData[$sLangPrefix]; + [$sExpectedLanguageCode, $sExpectedEnglishLanguageDesc, $aExpectedLocalizedLanguageDesc] = $aPrefixToLanguageData[$sLangPrefix]; $sDictPHP = file_get_contents($sDictFile); $iCount = preg_match_all("@Dict::Add\('(.*)'\s*,\s*'(.*)'\s*,\s*'(.*)'@", $sDictPHP, $aMatches); @@ -76,8 +79,12 @@ class DictionariesConsistencyTest extends ItopTestCase static::assertSame($sExpectedEnglishLanguageDesc, $sEnglishLanguageDesc, "Unexpected language description (english) for Dict::Add in dictionary $sDictFile"); } - foreach ($aMatches[3] as $sLocalizedLanguageDesc) { - static::assertSame($sExpectedLocalizedLanguageDesc, $sLocalizedLanguageDesc, + foreach ($aMatches[3] as $sLocalizedLanguageDesc) + { + if (false === is_array($aExpectedLocalizedLanguageDesc)) { + $aExpectedLocalizedLanguageDesc = array($aExpectedLocalizedLanguageDesc); + } + static::assertContains($sLocalizedLanguageDesc,$aExpectedLocalizedLanguageDesc, "Unexpected language description for Dict::Add in dictionary $sDictFile"); } }