mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-02 23:18:43 +02:00
Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts: # core/attributedef.class.inc.php # core/config.class.inc.php # core/htmlsanitizer.class.inc.php # sources/Renderer/RenderingOutput.php # test/core/sanitizer/HTMLDOMSanitizerTest.php # test/integration/DictionariesConsistencyTest.php
This commit is contained in:
@@ -8130,6 +8130,25 @@ class AttributeImage extends AttributeBlob
|
|||||||
return "Image";
|
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
|
* Check that the supplied ormDocument actually contains an image
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|||||||
@@ -1125,6 +1125,14 @@ class Config
|
|||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'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' => [
|
'inline_image_max_display_width' => [
|
||||||
'type' => 'integer',
|
'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.',
|
'description' => 'The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.',
|
||||||
|
|||||||
@@ -97,64 +97,163 @@ class HTMLNullSanitizer extends HTMLSanitizer
|
|||||||
{
|
{
|
||||||
return $sHTML;
|
return $sHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang
|
* Common implementation for sanitizer using DOM parsing
|
||||||
* Complete but quite slow
|
|
||||||
* http://htmlpurifier.org
|
|
||||||
*/
|
*/
|
||||||
/*
|
abstract class DOMSanitizer extends HTMLSanitizer
|
||||||
class HTMLPurifierSanitizer extends HTMLSanitizer
|
|
||||||
{
|
{
|
||||||
protected static $oPurifier = null;
|
/** @var DOMDocument */
|
||||||
|
protected $oDoc;
|
||||||
|
|
||||||
public function __construct()
|
abstract public function GetTagsWhiteList();
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
$oPurifierConfig = HTMLPurifier_Config::createDefault();
|
abstract public function GetTagsBlackList();
|
||||||
$oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8'
|
|
||||||
$oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional'
|
abstract public function GetAttrsWhiteList();
|
||||||
$oPurifierConfig->set('URI.AllowedSchemes', array (
|
|
||||||
'http' => true,
|
abstract public function GetAttrsBlackList();
|
||||||
'https' => true,
|
|
||||||
'data' => true, // This one is not present by default
|
abstract public function GetStylesWhiteList();
|
||||||
));
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function DoSanitize($sHTML)
|
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 <p><o:p></o:p></p>
|
||||||
|
// We have to transform that into <p><br></p> (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('@<o:p>(\s| )*</o:p>@', '<br>', $sHTML);
|
||||||
|
|
||||||
|
$this->LoadDoc($sHTML);
|
||||||
|
|
||||||
|
$this->CleanNode($this->oDoc);
|
||||||
|
|
||||||
|
$sCleanHtml = $this->PrintDoc();
|
||||||
|
|
||||||
return $sCleanHtml;
|
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
|
* @var array
|
||||||
* @see https://www.itophub.io/wiki/page?id=2_6_0%3Aadmin%3Arich_text_limitations
|
* @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',
|
'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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__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 <p><o:p></o:p></p>
|
|
||||||
// We have to transform that into <p><br></p> (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('@<o:p>(\s| )*</o:p>@', '<br>', $sHTML);
|
|
||||||
// Replace badly encoded non breaking space
|
|
||||||
$sHTML = preg_replace('~\xc2\xa0~', ' ', $sHTML);
|
|
||||||
|
|
||||||
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
|
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$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);
|
$oXPath = new DOMXPath($this->oDoc);
|
||||||
$sXPath = "//body";
|
$sXPath = "//body";
|
||||||
$oNodesList = $oXPath->query($sXPath);
|
$oNodesList = $oXPath->query($sXPath);
|
||||||
|
|
||||||
if ($oNodesList->length == 0)
|
if ($oNodesList->length == 0) {
|
||||||
{
|
|
||||||
// No body, save the whole document
|
// No body, save the whole document
|
||||||
$sCleanHtml = $this->oDoc->saveHTML();
|
$sCleanHtml = $this->oDoc->saveHTML();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Export only the content of the body tag
|
// Export only the content of the body tag
|
||||||
$sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0));
|
$sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0));
|
||||||
// remove the body tag itself
|
// remove the body tag itself
|
||||||
$sCleanHtml = str_replace( array('<body>', '</body>'), '', $sCleanHtml);
|
$sCleanHtml = str_replace(array('<body>', '</body>'), '', $sCleanHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $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();
|
return [];
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function CleanStyle($sStyle)
|
/**
|
||||||
|
* @return string[]
|
||||||
|
* @link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script
|
||||||
|
*/
|
||||||
|
public function GetTagsBlackList()
|
||||||
{
|
{
|
||||||
$aAllowedStyles = array();
|
return [
|
||||||
$aItems = explode(';', $sStyle);
|
'script',
|
||||||
{
|
];
|
||||||
foreach($aItems as $sItem)
|
|
||||||
{
|
|
||||||
$aElements = explode(':', trim($sItem));
|
|
||||||
if (in_array(trim(strtolower($aElements[0])), static::$aStylesWhiteList))
|
|
||||||
{
|
|
||||||
$aAllowedStyles[] = trim($sItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return implode(';', $aAllowedStyles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function IsValidAttributeContent($sAttributeName, $sValue)
|
public function GetAttrsWhiteList()
|
||||||
{
|
{
|
||||||
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))
|
return [];
|
||||||
{
|
}
|
||||||
return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue);
|
|
||||||
}
|
/**
|
||||||
return true;
|
* @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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
public function GetTags()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,18 +107,36 @@ EOF
|
|||||||
// ... in view mode
|
// ... in view mode
|
||||||
else
|
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('<div class="form-control-static">')
|
$oOutput->AddHtml('<div class="form-control-static">')
|
||||||
->AddHtml('<span class="label-group">');
|
->AddHtml('<span class="label-group">');
|
||||||
foreach($aItems as $sItemCode => $oItem)
|
foreach($aItems as $sItemCode => $value)
|
||||||
{
|
{
|
||||||
$sItemLabel = $oItem->Get('label');
|
$fExtractTagData($value, $sItemLabel, $sItemDescription);
|
||||||
$sItemDescription = $oItem->Get('description');
|
|
||||||
|
$sDescriptionAttr = (empty($sItemDescription))
|
||||||
|
? ''
|
||||||
|
: ' data-description="'.utils::HtmlEntities($sItemDescription).'"';
|
||||||
|
|
||||||
$oOutput->AddHtml('<span class="label label-default" data-code="'.$sItemCode.'" data-label="')
|
$oOutput->AddHtml('<span class="label label-default" data-code="'.$sItemCode.'" data-label="')
|
||||||
->AddHtml($sItemLabel, true)
|
->AddHtml($sItemLabel, true)
|
||||||
->AddHtml('" data-description="')
|
->AddHtml('"')
|
||||||
->AddHtml($sItemDescription, true)
|
->AddHtml($sDescriptionAttr)
|
||||||
->AddHtml('">')
|
->AddHtml('>')
|
||||||
->AddHtml($sItemLabel, true)
|
->AddHtml($sItemLabel, true)
|
||||||
->AddHtml('</span>');
|
->AddHtml('</span>');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
namespace Combodo\iTop\Renderer;
|
namespace Combodo\iTop\Renderer;
|
||||||
|
|
||||||
|
use utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description of RenderingOutput
|
* Description of RenderingOutput
|
||||||
*
|
*
|
||||||
@@ -111,15 +113,15 @@ class RenderingOutput
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param string $sHtml
|
* @param ?string $sHtml
|
||||||
* @param bool $bEncodeHtmlEntities
|
* @param bool $bEscapeHtmlEntities
|
||||||
*
|
*
|
||||||
* @return \Combodo\iTop\Renderer\RenderingOutput
|
* @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)) {
|
if (!is_null($sHtml)) {
|
||||||
$this->sHtml .= ($bEncodeHtmlEntities) ? htmlentities($sHtml, ENT_QUOTES, 'UTF-8') : $sHtml;
|
$this->sHtml .= ($bEscapeHtmlEntities) ? utils::Escapehtml($sHtml) : $sHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|||||||
75
test/core/sanitizer/AbstractDOMSanitizerTest.php
Normal file
75
test/core/sanitizer/AbstractDOMSanitizerTest.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Core\Sanitizer;
|
||||||
|
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
*/
|
||||||
|
abstract class AbstractDOMSanitizerTest extends ItopTestCase
|
||||||
|
{
|
||||||
|
const INPUT_DIRECTORY = 'input';
|
||||||
|
const OUTPUT_DIRECTORY = 'output';
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
require_once(APPROOT.'application/utils.inc.php');
|
||||||
|
require_once(APPROOT.'core/htmlsanitizer.class.inc.php');
|
||||||
|
require_once(APPROOT.'test/core/sanitizer/InlineImageMock.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function ReadTestFile($sFileToTest, $sFolderName)
|
||||||
|
{
|
||||||
|
$sCurrentPath = __DIR__;
|
||||||
|
|
||||||
|
return file_get_contents($sCurrentPath.DIRECTORY_SEPARATOR
|
||||||
|
.$sFolderName.DIRECTORY_SEPARATOR
|
||||||
|
.$sFileToTest);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function RemoveNewLines($sText)
|
||||||
|
{
|
||||||
|
$sText = str_replace("\r\n", "\n", $sText);
|
||||||
|
$sText = str_replace("\r", "\n", $sText);
|
||||||
|
$sText = str_replace("\n", '', $sText);
|
||||||
|
|
||||||
|
return $sText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
protected 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.'"';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function IsClosingTag($sTag)
|
||||||
|
{
|
||||||
|
if (in_array($sTag, array('br', 'img', 'hr'))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Core\Sanitizer;
|
||||||
|
|
||||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
|
||||||
|
|
||||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
|
||||||
use HTMLDOMSanitizer;
|
use HTMLDOMSanitizer;
|
||||||
|
|
||||||
|
|
||||||
|
require_once __DIR__.'/AbstractDOMSanitizerTest.php';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @runTestsInSeparateProcesses
|
* @runTestsInSeparateProcesses
|
||||||
* @preserveGlobalState disabled
|
* @preserveGlobalState disabled
|
||||||
* @backupGlobals disabled
|
* @backupGlobals disabled
|
||||||
*/
|
*/
|
||||||
class HTMLDOMSanitizerTest extends ItopTestCase
|
class HTMLDOMSanitizerTest extends AbstractDOMSanitizerTest
|
||||||
{
|
{
|
||||||
const INPUT_DIRECTORY = 'sanitizer/input';
|
|
||||||
const OUTPUT_DIRECTORY = 'sanitizer/output';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider DoSanitizeProvider
|
* @dataProvider DoSanitizeProvider
|
||||||
*
|
*
|
||||||
@@ -41,34 +40,15 @@ class HTMLDOMSanitizerTest extends ItopTestCase
|
|||||||
$this->assertEquals($sOutputHtml, $sRes);
|
$this->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()
|
public function DoSanitizeProvider()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
'utf-8_wrong_character_email_truncated.txt',
|
'scripts.html',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider WhiteListProvider
|
* @dataProvider WhiteListProvider
|
||||||
*
|
*
|
||||||
@@ -146,13 +126,11 @@ class HTMLDOMSanitizerTest extends ItopTestCase
|
|||||||
$aTestCaseArray = array();
|
$aTestCaseArray = array();
|
||||||
|
|
||||||
$sInputText = $this->ReadTestFile('whitelist_test.html', self::INPUT_DIRECTORY);
|
$sInputText = $this->ReadTestFile('whitelist_test.html', self::INPUT_DIRECTORY);
|
||||||
foreach ($aTagsWhiteList as $sTag => $aTagAttributes)
|
foreach ($aTagsWhiteList as $sTag => $aTagAttributes) {
|
||||||
{
|
|
||||||
$sTestCaseText = $sInputText;
|
$sTestCaseText = $sInputText;
|
||||||
$sStartTag = "<$sTag";
|
$sStartTag = "<$sTag";
|
||||||
$iAttrCounter = 0;
|
$iAttrCounter = 0;
|
||||||
foreach ($aTagAttributes as $sTagAttribute)
|
foreach ($aTagAttributes as $sTagAttribute) {
|
||||||
{
|
|
||||||
$sStartTag .= $this->GetTagAttributeValue($sTagAttribute, $iAttrCounter);
|
$sStartTag .= $this->GetTagAttributeValue($sTagAttribute, $iAttrCounter);
|
||||||
$iAttrCounter++;
|
$iAttrCounter++;
|
||||||
}
|
}
|
||||||
@@ -168,41 +146,6 @@ class HTMLDOMSanitizerTest extends ItopTestCase
|
|||||||
return $aTestCaseArray;
|
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
|
* @dataProvider RemoveBlackListedTagContentProvider
|
||||||
*/
|
*/
|
||||||
53
test/core/sanitizer/SvgDOMSanitizerTest.php
Normal file
53
test/core/sanitizer/SvgDOMSanitizerTest.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Core\Sanitizer;
|
||||||
|
|
||||||
|
use SvgDOMSanitizer;
|
||||||
|
|
||||||
|
|
||||||
|
require_once __DIR__.'/AbstractDOMSanitizerTest.php';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
*/
|
||||||
|
class SvgDOMSanitizerTest extends AbstractDOMSanitizerTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider DoSanitizeProvider
|
||||||
|
*
|
||||||
|
* @param string $sFileToTest filename
|
||||||
|
*/
|
||||||
|
public function testDoSanitize($sFileToTest)
|
||||||
|
{
|
||||||
|
$sInputHtml = $this->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',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
7
test/core/sanitizer/input/scripts.html
Normal file
7
test/core/sanitizer/input/scripts.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<h1>Test with lots of JS scripts to filter !</h1>
|
||||||
|
|
||||||
|
<p><img src="http://toto.invalid/" onerror="alert('hello world !');"></p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
alert("hello world !");
|
||||||
|
</script>
|
||||||
8
test/core/sanitizer/input/scripts.svg
Normal file
8
test/core/sanitizer/input/scripts.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" onload="alert('hello world !');">
|
||||||
|
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)"/>
|
||||||
|
<script type="text/javascript">
|
||||||
|
alert("XSS");
|
||||||
|
</script>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 418 B |
3
test/core/sanitizer/output/scripts.html
Normal file
3
test/core/sanitizer/output/scripts.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<h1>Test with lots of JS scripts to filter !</h1>
|
||||||
|
|
||||||
|
<p><img src="http://toto.invalid/"></p>
|
||||||
1
test/core/sanitizer/output/scripts.svg
Normal file
1
test/core/sanitizer/output/scripts.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full"><rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)"/></svg>
|
||||||
|
After Width: | Height: | Size: 305 B |
@@ -34,7 +34,10 @@ class DictionariesConsistencyTest extends ItopTestCase
|
|||||||
'da' => array('DA DA', 'Danish', 'Dansk'),
|
'da' => array('DA DA', 'Danish', 'Dansk'),
|
||||||
'de' => array('DE DE', 'German', 'Deutsch'),
|
'de' => array('DE DE', 'German', 'Deutsch'),
|
||||||
'en' => array('EN US', 'English', 'English'),
|
'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'),
|
'fr' => array('FR FR', 'French', 'Français'),
|
||||||
'hu' => array('HU HU', 'Hungarian', 'Magyar'),
|
'hu' => array('HU HU', 'Hungarian', 'Magyar'),
|
||||||
'it' => array('IT IT', 'Italian', 'Italiano'),
|
'it' => array('IT IT', 'Italian', 'Italiano'),
|
||||||
@@ -57,7 +60,7 @@ class DictionariesConsistencyTest extends ItopTestCase
|
|||||||
static::fail("Unknown prefix '$sLangPrefix' for dictionary file '$sDictFile'");
|
static::fail("Unknown prefix '$sLangPrefix' for dictionary file '$sDictFile'");
|
||||||
}
|
}
|
||||||
|
|
||||||
[$sExpectedLanguageCode, $sExpectedEnglishLanguageDesc, $sExpectedLocalizedLanguageDesc] = $aPrefixToLanguageData[$sLangPrefix];
|
[$sExpectedLanguageCode, $sExpectedEnglishLanguageDesc, $aExpectedLocalizedLanguageDesc] = $aPrefixToLanguageData[$sLangPrefix];
|
||||||
|
|
||||||
$sDictPHP = file_get_contents($sDictFile);
|
$sDictPHP = file_get_contents($sDictFile);
|
||||||
$iCount = preg_match_all("@Dict::Add\('(.*)'\s*,\s*'(.*)'\s*,\s*'(.*)'@", $sDictPHP, $aMatches);
|
$iCount = preg_match_all("@Dict::Add\('(.*)'\s*,\s*'(.*)'\s*,\s*'(.*)'@", $sDictPHP, $aMatches);
|
||||||
@@ -76,8 +79,12 @@ class DictionariesConsistencyTest extends ItopTestCase
|
|||||||
static::assertSame($sExpectedEnglishLanguageDesc, $sEnglishLanguageDesc,
|
static::assertSame($sExpectedEnglishLanguageDesc, $sEnglishLanguageDesc,
|
||||||
"Unexpected language description (english) for Dict::Add in dictionary $sDictFile");
|
"Unexpected language description (english) for Dict::Add in dictionary $sDictFile");
|
||||||
}
|
}
|
||||||
foreach ($aMatches[3] as $sLocalizedLanguageDesc) {
|
foreach ($aMatches[3] as $sLocalizedLanguageDesc)
|
||||||
static::assertSame($sExpectedLocalizedLanguageDesc, $sLocalizedLanguageDesc,
|
{
|
||||||
|
if (false === is_array($aExpectedLocalizedLanguageDesc)) {
|
||||||
|
$aExpectedLocalizedLanguageDesc = array($aExpectedLocalizedLanguageDesc);
|
||||||
|
}
|
||||||
|
static::assertContains($sLocalizedLanguageDesc,$aExpectedLocalizedLanguageDesc,
|
||||||
"Unexpected language description for Dict::Add in dictionary $sDictFile");
|
"Unexpected language description for Dict::Add in dictionary $sDictFile");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user