Merge branch 'feature/bug2996-checklist-compilation' into feature/fast-theme-compilation

This commit is contained in:
odain
2020-06-08 09:39:33 +02:00
90 changed files with 594 additions and 129 deletions

View File

@@ -25,7 +25,6 @@ require_once (__DIR__.DIRECTORY_SEPARATOR.'update.classes.inc.php');
/** @var \FileVersionUpdater[] $aFilesUpdaters */
$aFilesUpdaters = array(
new iTopVersionFileUpdater(),
new CssVariablesFileUpdater(),
new DatamodelsModulesFiles(),
);

View File

@@ -89,26 +89,6 @@ class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater
}
}
class CssVariablesFileUpdater extends AbstractSingleFileVersionUpdater
{
public function __construct()
{
parent::__construct('css/css-variables.scss');
}
/**
* @inheritDoc
*/
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
{
return preg_replace(
'/(\$version: "v)[^"]*(";)/',
'${1}'.$sVersionLabel.'${2}',
$sFileContent
);
}
}
abstract class AbstractGlobFileVersionUpdater extends FileVersionUpdater
{
protected $sGlobPattern;

View File

@@ -25,6 +25,8 @@
*/
class ThemeHandler
{
const IMAGE_EXTENSIONS = array('png', 'gif', 'jpg', 'jpeg');
private static $oCompileCSSService;
/**
* Return default theme name and parameters
@@ -173,12 +175,15 @@ class ThemeHandler
$iStyleLastModified = 0;
clearstatcache();
// Loading files to import and stylesheet to compile, also getting most recent modification time on overall files
$aStylesheetFiles = array();
foreach ($aThemeParameters['imports'] as $sImport)
{
$sTmpThemeScssContent .= '@import "'.$sImport.'";'."\n";
$sFile = static::FindStylesheetFile($sImport, $aImportsPaths);
$iImportLastModified = @filemtime($sFile);
$aStylesheetFiles[] = $sFile;
$iStyleLastModified = $iStyleLastModified < $iImportLastModified ? $iImportLastModified : $iStyleLastModified;
}
foreach ($aThemeParameters['stylesheets'] as $sStylesheet)
@@ -187,9 +192,24 @@ class ThemeHandler
$sFile = static::FindStylesheetFile($sStylesheet, $aImportsPaths);
$iStylesheetLastModified = @filemtime($sFile);
$aStylesheetFiles[] = $sFile;
$iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified;
}
$aIncludedImages=static::GetIncludedImages($aThemeParameters['variables'], $aStylesheetFiles, $sThemeFolderPath);
foreach ($aIncludedImages as $sImage)
{
if (!is_file($sImage))
{
IssueLog::Warning("Cannot find $sImage during SCSS $sThemeId precompilation");
}
else
{
$iStylesheetLastModified = @filemtime($sImage);
$iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified;
}
}
// Checking if our compiled css is outdated
$iFilemetime = @filemtime($sThemeCssPath);
$bFileExists = file_exists($sThemeCssPath);
@@ -208,7 +228,7 @@ class ThemeHandler
if (!$bFileExists || $bVarSignatureChanged || (is_writable($sThemeFolderPath) && ($iFilemetime < $iStyleLastModified)))
{
// Dates don't match. Second chance: check if the already compiled stylesheet exists and is consistent based on its signature
$sActualSignature = static::ComputeSignature($aThemeParameters, $aImportsPaths);
$sActualSignature = static::ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages);
if ($bFileExists && !$bSetup)
{
@@ -232,11 +252,11 @@ $sActualSignature
*/
CSS;
if (!self::$oCompileCSSService)
if (!static::$oCompileCSSService)
{
self::$oCompileCSSService = new CompileCSSService();
static::$oCompileCSSService = new CompileCSSService();
}
$sTmpThemeCssContent = self::$oCompileCSSService->CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths,
$sTmpThemeCssContent = static::$oCompileCSSService->CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths,
$aThemeParameters['variables']);
file_put_contents($sThemeCssPath, $sSignatureComment.$sTmpThemeCssContent);
}
@@ -251,16 +271,18 @@ CSS;
*
* @param string[] $aThemeParameters
* @param string[] $aImportsPaths
* @param string[] $aIncludedImages
*
* @return string
* @throws \Exception
*/
public static function ComputeSignature($aThemeParameters, $aImportsPaths)
public static function ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages)
{
$aSignature = array(
'variables' => md5(json_encode($aThemeParameters['variables'])),
'stylesheets' => array(),
'imports' => array(),
'images' => array(),
);
foreach ($aThemeParameters['imports'] as $key => $sImport)
@@ -273,9 +295,279 @@ CSS;
$sFile = static::FindStylesheetFile($sStylesheet, $aImportsPaths);
$aSignature['stylesheets'][$key] = md5_file($sFile);
}
foreach ($aIncludedImages as $sImage)
{
if (is_file($sImage))
{
$aSignature['images'][$sImage] = md5_file($sImage);
}
}
return json_encode($aSignature);
}
/**
* Search for images referenced in stylesheet files
* @param array $aThemeParametersVariables
* @param array $aStylesheetFiles
* @param string $sThemeFolderPath : used as relative paths to find css images
*
* @return array
* @since 2.8.0
*/
public static function GetIncludedImages($aThemeParametersVariables, $aStylesheetFiles, $sThemeFolderPath)
{
$aCompleteUrls = array();
$aToCompleteUrls = array();
$aMissingVariables = array();
$aFoundVariables = array();
$aMap = array(
'aCompleteUrls' => $aCompleteUrls,
'aToCompleteUrls' => $aToCompleteUrls,
'aMissingVariables' => $aMissingVariables,
'aFoundVariables' => $aFoundVariables,
);
foreach ($aStylesheetFiles as $sStylesheetFile)
{
$aRes = static::GetAllUrlFromScss($aThemeParametersVariables, $sStylesheetFile);
/** @var array $aVal */
foreach($aMap as $key => $aVal)
{
if (array_key_exists($key, $aMap))
{
$aMap[$key] = array_merge($aVal, $aRes[$key]);
}
}
}
$aMap = static::ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFiles);
$aImages = array();
foreach ($aMap ['aCompleteUrls'] as $sUrl)
{
$sImg = $sUrl;
if (preg_match("/(.*)\?/", $sUrl, $aMatches))
{
$sImg=$aMatches[1];
}
if (static::HasImageExtension($sImg)
&& ! array_key_exists($sImg, $aImages))
{
if (!is_file($sImg))
{
$sImg=$sThemeFolderPath.DIRECTORY_SEPARATOR.$sImg;
}
$aImages[$sImg]=$sImg;
}
}
return array_values($aImages);
}
/**
* Complete url using provided variables. Example with $var=1: XX + $var => XX1
* @param $aMap
* @param $aThemeParametersVariables
* @param $aStylesheetFile
*
* @return mixed
*/
public static function ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFile)
{
$sContent="";
foreach ($aStylesheetFile as $sStylesheetFile)
{
if (is_file($sStylesheetFile))
{
$sContent .= '\n' . file_get_contents($sStylesheetFile);
}
}
$aMissingVariables=$aMap['aMissingVariables'];
$aFoundVariables=$aMap['aFoundVariables'];
$aToCompleteUrls=$aMap['aToCompleteUrls'];
$aCompleteUrls=$aMap['aCompleteUrls'];
list($aMissingVariables, $aFoundVariables) = static::FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent);
list($aToCompleteUrls, $aCompleteUrls) = static::ResolveUrls($aFoundVariables, $aToCompleteUrls, $aCompleteUrls);
$aMap['aMissingVariables']=$aMissingVariables;
$aMap['aFoundVariables']=$aFoundVariables;
$aMap['aToCompleteUrls']=$aToCompleteUrls;
$aMap['aCompleteUrls']=$aCompleteUrls;
return $aMap;
}
/**
* Find missing variable values from SCSS content based on their name.
* @param $aThemeParametersVariables
* @param $aMissingVariables
* @param $aFoundVariables
* @param $sContent: scss content
*
* @return array
*/
public static function FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent)
{
if (!empty($aMissingVariables))
{
foreach ($aMissingVariables as $var)
{
if (array_key_exists($var, $aThemeParametersVariables))
{
$aFoundVariables[$var] = $aThemeParametersVariables[$var];
unset($aMissingVariables[$var]);
}
else
{
if (preg_match_all("/\\\$$var\s*:\s*[\"'](.*)[\"']/", $sContent, $aValues))
{
$aFoundVariables[$var] = $aValues[1][0];
unset($aMissingVariables[$var]);
}
}
}
}
return array($aMissingVariables, $aFoundVariables);
}
/**
* @param $aFoundVariables
* @param array $aToCompleteUrls
* @param array $aCompleteUrls
*
* @return array
*/
public static function ResolveUrls($aFoundVariables, array $aToCompleteUrls, array $aCompleteUrls)
{
if (!empty($aFoundVariables))
{
foreach ($aToCompleteUrls as $sUrlTemplate)
{
unset($aToCompleteUrls[$sUrlTemplate]);
$sResolvedUrl = static::ResolveUrl($sUrlTemplate, $aFoundVariables);
if ($sResolvedUrl == false)
{
$aToCompleteUrls[$sUrlTemplate] = $sUrlTemplate;
}
else
{
$aCompleteUrls[$sUrlTemplate] = $sResolvedUrl;
}
}
}
return array($aToCompleteUrls, $aCompleteUrls);
}
/**
* Find all referenced URLs from a SCSS file.
* @param $aThemeParametersVariables
* @param $sStylesheetFile
*
* @return array
*/
public static function GetAllUrlFromScss($aThemeParametersVariables, $sStylesheetFile)
{
$aCompleteUrls = array();
$aToCompleteUrls = array();
$aMissingVariables = array();
$aFoundVariables = array();
if (is_file($sStylesheetFile))
{
$sContent = file_get_contents($sStylesheetFile);
if (preg_match_all("/url\s*\((.*)\)/", $sContent, $aMatches))
{
foreach ($aMatches[1] as $path)
{
if (!array_key_exists($path, $aCompleteUrls)
&& !array_key_exists($path, $aToCompleteUrls))
{
if (preg_match_all("/\\$([\w-_]+)/", $path, $aCurrentVars))
{
/** @var string $aCurrentVars */
foreach ($aCurrentVars[1] as $var)
{
if (!array_key_exists($var, $aMissingVariables))
{
$aMissingVariables[$var] = $var;
}
}
$aToCompleteUrls[$path] = $path;
}
else
{
$aCompleteUrls[$path] = trim($path, "\"'");
}
}
}
}
if (!empty($aMissingVariables))
{
list($aMissingVariables, $aFoundVariables) = static::FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent);
list($aToCompleteUrls, $aCompleteUrls) = static::ResolveUrls($aFoundVariables, $aToCompleteUrls, $aCompleteUrls);
}
}
return array(
'aCompleteUrls' => $aCompleteUrls,
'aToCompleteUrls' => $aToCompleteUrls,
'aMissingVariables' => $aMissingVariables,
'aFoundVariables' => $aFoundVariables,
);
}
/**
* Calculate url based on its template + variables.
* @param $sUrlTemplate
* @param $aFoundVariables
*
* @return bool|string
*/
public static function ResolveUrl($sUrlTemplate, $aFoundVariables)
{
$aPattern=array();
$aReplacement=array();
foreach ($aFoundVariables as $aFoundVariable => $aFoundVariableValue)
{
//XX + $key + YY
$aPattern[]="/['\"]\s*\+\s*\\\$" . $aFoundVariable . "[\s\+]+\s*['\"]/";
$aReplacement[]=$aFoundVariableValue;
//$key + YY
$aPattern[]="/\\\$" . $aFoundVariable. "[\s\+]+\s*['\"]/";
$aReplacement[]=$aFoundVariableValue;
//XX + $key
$aPattern[]="/['\"]\s*[\+\s]+\\\$" . $aFoundVariable . "$/";
$aReplacement[]=$aFoundVariableValue;
}
$sResolvedUrl=preg_replace($aPattern, $aReplacement, $sUrlTemplate);
if (strpos($sResolvedUrl, "+")!=false)
{
return false;
}
return trim($sResolvedUrl, "\"'");
}
/**
* indicate whether a string ends with image suffix.
* @param $path
*
* @return bool
*/
private static function HasImageExtension($path)
{
foreach (static::IMAGE_EXTENSIONS as $sExt)
{
if (endsWith($path, $sExt))
{
return true;
}
}
return false;
}
/**
* Extract the signature for a generated CSS file. The signature MUST be alone one line immediately
* followed (on the next line) by the === SIGNATURE END === pattern
@@ -322,7 +614,7 @@ CSS;
* @throws Exception
* @return string
*/
private static function FindStylesheetFile($sFile, $aImportsPaths)
public static function FindStylesheetFile($sFile, $aImportsPaths)
{
foreach($aImportsPaths as $sPath)
{
@@ -337,7 +629,7 @@ CSS;
public static function mockCompileCSSService($oCompileCSSServiceMock)
{
self::$oCompileCSSService = $oCompileCSSServiceMock;
static::$oCompileCSSService = $oCompileCSSServiceMock;
}
}

View File

@@ -1858,17 +1858,13 @@ class utils
}
/**
* @deprecated
* @param string $sModuleName
* @return string|NULL compiled version of a given module, as it was seen by the compiler
*/
public static function GetCompiledModuleVersion($sModuleName)
{
$aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
return static::GetCacheBusterTimestamp();
}
/**

View File

@@ -15,9 +15,6 @@
*
* You should have received a copy of the GNU Affero General Public License
*/
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.7.0-1";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors

View File

@@ -1,6 +1,6 @@
/*
=== SIGNATURE BEGIN ===
{"variables":"d751713988987e9331980363e24189ce","stylesheets":{"css-variables":"934888ebb4991d4c76555be6b6d1d5cc","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea"},"imports":[]}
{"variables":"d751713988987e9331980363e24189ce","stylesheets":{"css-variables":"1d4b4ae2a6fba3db101f8dd1cecab082","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"0dd837aeddc3f407c980e0b20f06dd4c"},"imports":[],"images":[]}
=== SIGNATURE END ===
*/
/*!

View File

@@ -1,6 +1,6 @@
/*
=== SIGNATURE BEGIN ===
{"variables":"8cfe86f2c55d8eff36d57eb4e83d89f1","stylesheets":{"css-variables":"934888ebb4991d4c76555be6b6d1d5cc","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea","environment-banner":"3de3ffb8232b9a649e912b570a64bf5d"},"imports":[]}
{"variables":"8cfe86f2c55d8eff36d57eb4e83d89f1","stylesheets":{"css-variables":"1d4b4ae2a6fba3db101f8dd1cecab082","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"0dd837aeddc3f407c980e0b20f06dd4c","environment-banner":"3de3ffb8232b9a649e912b570a64bf5d"},"imports":[],"images":[]}
=== SIGNATURE END ===
*/
/*!

View File

@@ -14,6 +14,7 @@ class ThemeHandlerTest extends ItopTestCase
private $cssPath;
private $jsonThemeParamFile;
private $tmpDir;
private $aDirsToCleanup=array();
public function setUp()
{
@@ -25,6 +26,7 @@ class ThemeHandlerTest extends ItopTestCase
ThemeHandler::mockCompileCSSService($this->compileCSSServiceMock);
$this->tmpDir=$this->tmpdir();
$aDirsToCleanup[] = $this->tmpDir;
if (!is_dir($this->tmpDir ."/branding"))
{
@@ -37,6 +39,30 @@ class ThemeHandlerTest extends ItopTestCase
$this->recurse_copy(APPROOT."/test/application/theme-handler/expected/css", $this->tmpDir."/branding/css");
}
public function tearDown()
{
parent::tearDown();
foreach ($this->aDirsToCleanup as $dir)
{
$this->rrmdir($dir);
}
}
function rrmdir($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (is_dir($dir."/".$object))
$this->rrmdir($dir."/".$object);
else
unlink($dir."/".$object);
}
}
rmdir($dir);
}
}
function tmpdir() {
$tmpfile=tempnam(sys_get_temp_dir(),'');
if (file_exists($tmpfile))
@@ -68,11 +94,114 @@ class ThemeHandlerTest extends ItopTestCase
closedir($dir);
}
/**
* Test used to be notified by CI when precompiled styles are not up to date anymore in code repository.
* @param $xmlDataCusto
* @dataProvider providePrecompiledStyleSheets
* @throws \Exception
*/
public function testValidatePrecompiledStyles($xmlDataCusto)
{
echo "=== datamodel custo: $xmlDataCusto\n";
$oDom = new MFDocument();
$oDom->load($xmlDataCusto);
/**DOMNodeList **/$oThemeNodes=$oDom->GetNodes("/itop_design/branding/themes/theme");
$this->assertNotNull($oThemeNodes);
// Parsing themes from DM
foreach($oThemeNodes as $oTheme)
{
$sPrecompiledStylesheet = $oTheme->GetChildText('precompiled_stylesheet', '');
if (empty($sPrecompiledStylesheet))
{
continue;
}
$sThemeId = $oTheme->getAttribute('id');
echo "=== theme: $sThemeId ===\n";
$precompiledSig= ThemeHandler::GetSignature(dirname(__FILE__)."/../../datamodels/2.x/".$sPrecompiledStylesheet);
echo " precompiled signature: $precompiledSig\n";
$this->assertFalse(empty($precompiledSig), "Signature in precompiled theme '".$sThemeId."' is not retrievable (cf precompiledsheet $sPrecompiledStylesheet / datamodel $xmlDataCusto)");
$aThemeParameters = array(
'variables' => array(),
'imports' => array(),
'stylesheets' => array(),
'precompiled_stylesheet' => '',
);
$aThemeParameters['precompiled_stylesheet'] = $sPrecompiledStylesheet;
/** @var \DOMNodeList $oVariables */
$oVariables = $oTheme->GetNodes('variables/variable');
foreach($oVariables as $oVariable)
{
$sVariableId = $oVariable->getAttribute('id');
$aThemeParameters['variables'][$sVariableId] = $oVariable->GetText();
}
/** @var \DOMNodeList $oImports */
$aStylesheetFiles = array();
$aImportsPaths = array(APPROOT.'datamodels');
$oImports = $oTheme->GetNodes('imports/import');
foreach($oImports as $oImport)
{
$sImportId = $oImport->getAttribute('id');
$aThemeParameters['imports'][$sImportId] = $oImport->GetText();
$sFile = ThemeHandler::FindStylesheetFile($oImport->GetText(), $aImportsPaths);
$aStylesheetFiles[] = $sFile;
}
/** @var \DOMNodeList $oStylesheets */
$oStylesheets = $oTheme->GetNodes('stylesheets/stylesheet');
foreach($oStylesheets as $oStylesheet)
{
$sStylesheetId = $oStylesheet->getAttribute('id');
$aThemeParameters['stylesheets'][$sStylesheetId] = $oStylesheet->GetText();
$sFile = ThemeHandler::FindStylesheetFile($oStylesheet->GetText(), $aImportsPaths);
$aStylesheetFiles[] = $sFile;
}
$sThemeFolderPath = APPROOT.'env-production/branding/themes/'.$sThemeId.'/test';
if (!is_dir($sThemeFolderPath))
{
mkdir($sThemeFolderPath);
}
$aIncludedImages=ThemeHandler::GetIncludedImages($aThemeParameters['variables'], $aStylesheetFiles, $sThemeFolderPath);
$compiled_json_sig = ThemeHandler::ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages);
echo " current signature: $compiled_json_sig\n";
rmdir($sThemeFolderPath);
$this->assertEquals($precompiledSig, $compiled_json_sig, "Precompiled signature does not match currently compiled one on theme '".$sThemeId."' (cf precompiledsheet $sPrecompiledStylesheet / datamodel $xmlDataCusto)");
}
}
public function providePrecompiledStyleSheets()
{
$datamodelfiles=glob(dirname(__FILE__)."/../../datamodels/2.x/**/datamodel*.xml");
$test_set = array();
foreach ($datamodelfiles as $datamodelfile)
{
if (is_file($datamodelfile) &&
$datamodelfile=="/var/www/html/iTop/test/application/../../datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml")
{
$content=file_get_contents($datamodelfile);
if (strpos($content, "precompiled_stylesheet")!==false)
{
$test_set[$datamodelfile]=array($datamodelfile);
}
}
}
return $test_set;
}
public function testGetSignature()
{
$sig = ThemeHandler::GetSignature(APPROOT.'test/application/theme-handler/expected/themes/basque-red/main.css');
$expect_sig=<<<JSON
{"variables":"37c31105548fce44fecca5cb34e455c9","stylesheets":{"css-variables":"934888ebb4991d4c76555be6b6d1d5cc","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea"},"imports":[]}
{"variables":"37c31105548fce44fecca5cb34e455c9","stylesheets":{"css-variables":"934888ebb4991d4c76555be6b6d1d5cc","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea"},"imports":[],"images":[]}
JSON;
$this->assertEquals($expect_sig,$sig);
@@ -182,9 +311,31 @@ JSON;
*/
public function testCompileThemes($ThemeParametersJson, $CompileCSSFromSASSCount, $missingFile=0, $filesTouchedRecently=0, $fileMd5sumModified=0, $fileToTest=null, $expected_maincss_path=null, $bSetup=true)
{
$fileToTest=$this->tmpDir.'/'.$fileToTest;
if (is_file($this->tmpDir.'/'.$fileToTest))
{
$fileToTest=$this->tmpDir.'/'.$fileToTest;
}
else
{
$fileToTest=APPROOT.'/'.$fileToTest;
}
$cssPath = $this->tmpDir . '/branding/themes/basque-red/main.css';
copy(APPROOT . 'test/application/theme-handler/expected/themes/basque-red/main.css', $cssPath);
copy(APPROOT."test/application/theme-handler/expected/themes/basque-red/main_testcompilethemes.css", $cssPath);
$sAbsoluteImagePath = APPROOT .'test/application/theme-handler/copied/testimages/';
@mkdir(APPROOT .'test/application/theme-handler/copied/');
@mkdir($sAbsoluteImagePath);
$aDirsToCleanup[] = $sAbsoluteImagePath;
$this->recurse_copy(APPROOT .'test/application/theme-handler/expected/testimages/', $sAbsoluteImagePath);
//change approot-relative in css-variable to use absolute path
$sCssVarPath = $this->tmpDir."/branding/css/css-variables.scss";
$sCssVariableContent = file_get_contents($sCssVarPath);
$sLine = '$approot-relative: "' . $sAbsoluteImagePath . '" !default;';
$sCssVariableContent=preg_replace("/\\\$approot-relative: \"(.*)\"/", $sLine, $sCssVariableContent);
file_put_contents($sCssVarPath, $sCssVariableContent);
if ($missingFile==1)
{
@@ -223,14 +374,16 @@ JSON;
$modifiedVariableThemeParameterJson='{"variables":{"brand-primary1":"#C53030","hover-background-color":"#F6F6F6","icons-filter":"grayscale(1)","search-form-container-bg-color":"#4A5568"},"imports":{"css-variables":"..\/css\/css-variables.scss"},"stylesheets":{"jqueryui":"..\/css\/ui-lightness\/jqueryui.scss","main":"..\/css\/light-grey.scss"}}';
$initialThemeParamJson='{"variables":{"brand-primary":"#C53030","hover-background-color":"#F6F6F6","icons-filter":"grayscale(1)","search-form-container-bg-color":"#4A5568"},"imports":{"css-variables":"..\/css\/css-variables.scss"},"stylesheets":{"jqueryui":"..\/css\/ui-lightness\/jqueryui.scss","main":"..\/css\/light-grey.scss"}}';
$import_file_path = '/branding/css/css-variables.scss';
$importmodified_maincss="test/application/theme-handler/expected/themes/basque-red/main_importmodified.css";
$varchanged_maincss="test/application/theme-handler/expected/themes/basque-red/main_varchanged.css";
$stylesheet_maincss="test/application/theme-handler/expected/themes/basque-red/main_stylesheet.css";
$image_maincss="test/application/theme-handler/expected/themes/basque-red/main_imagemodified.css";
$importmodified_maincss="test/application/theme-handler/expected/themes/basque-red/main_importmodified.css";
$stylesheet_file_path = '/branding/css/light-grey.scss';
$image_file_path = 'test/application/theme-handler/copied/testimages/images/green-header.gif';
return array(
"setup context: variables list modified without any file touched" => array($modifiedVariableThemeParameterJson, 1,0,0,0,$import_file_path, $varchanged_maincss),
"setup context: variables list modified with files touched" => array($modifiedVariableThemeParameterJson, 1,0,1,0,$import_file_path, $varchanged_maincss, false),
"itop page/theme loading; variables list modified sans touch de fichier" => array($modifiedVariableThemeParameterJson, 0,0,0,0,$import_file_path, $varchanged_maincss, false),
"itop page/theme loading; variables list modified without any file touched" => array($modifiedVariableThemeParameterJson, 0,0,0,0,$import_file_path, $varchanged_maincss, false),
//imports
"import file missing" => array($initialThemeParamJson, 0, 1, 0, 0, $import_file_path),
"import file touched" => array($initialThemeParamJson, 0, 0, 1, 0, $import_file_path),
@@ -238,96 +391,81 @@ JSON;
//stylesheets
"stylesheets file missing" => array($initialThemeParamJson, 0, 1, 0, 0, $stylesheet_file_path),
"stylesheets file touched" => array($initialThemeParamJson, 0, 0, 1, 0, $stylesheet_file_path),
"stylesheets file modified" => array($initialThemeParamJson, 1, 0, 0, 1, $stylesheet_file_path, $stylesheet_maincss)
"stylesheets file modified" => array($initialThemeParamJson, 1, 0, 0, 1, $stylesheet_file_path, $stylesheet_maincss),
//images
"image file missing" => array($initialThemeParamJson, 0, 1, 0, 0, $image_file_path),
"image file touched" => array($initialThemeParamJson, 0, 0, 1, 0, $image_file_path),
"image file modified" => array($initialThemeParamJson, 1, 0, 0, 1, $image_file_path, $image_maincss),
);
}
/**
* @param $xmlDataCusto
* @dataProvider providePrecompiledStyleSheets
* @throws \Exception
* @param $sScssFile
*
* @dataProvider GetAllUrlFromScssProvider
*/
public function testValidatePrecompiledStyles($xmlDataCusto)
public function testGetAllUrlFromScss($sScssFile)
{
echo "=== datamodel custo: $xmlDataCusto\n";
$oDom = new MFDocument();
$oDom->load($xmlDataCusto);
/**DOMNodeList **/$oThemeNodes=$oDom->GetNodes("/itop_design/branding/themes/theme");
$this->assertNotNull($oThemeNodes);
// Parsing themes from DM
foreach($oThemeNodes as $oTheme)
{
$sPrecompiledStylesheet = $oTheme->GetChildText('precompiled_stylesheet', '');
if (empty($sPrecompiledStylesheet))
{
continue;
}
$sThemeId = $oTheme->getAttribute('id');
echo "=== theme: $sThemeId ===\n";
$precompiledSig= ThemeHandler::GetSignature(dirname(__FILE__)."/../../datamodels/2.x/".$sPrecompiledStylesheet);
echo " precompiled signature: $precompiledSig\n";
$this->assertFalse(empty($precompiledSig), "Signature in precompiled theme '".$sThemeId."' is not retrievable (cf precompiledsheet $sPrecompiledStylesheet / datamodel $xmlDataCusto)");
$aThemeParameters = array(
'variables' => array(),
'imports' => array(),
'stylesheets' => array(),
'precompiled_stylesheet' => '',
);
$aThemeParameters['precompiled_stylesheet'] = $sPrecompiledStylesheet;
/** @var \DOMNodeList $oVariables */
$oVariables = $oTheme->GetNodes('variables/variable');
foreach($oVariables as $oVariable)
{
$sVariableId = $oVariable->getAttribute('id');
$aThemeParameters['variables'][$sVariableId] = $oVariable->GetText();
}
/** @var \DOMNodeList $oImports */
$oImports = $oTheme->GetNodes('imports/import');
foreach($oImports as $oImport)
{
$sImportId = $oImport->getAttribute('id');
$aThemeParameters['imports'][$sImportId] = $oImport->GetText();
}
/** @var \DOMNodeList $oStylesheets */
$oStylesheets = $oTheme->GetNodes('stylesheets/stylesheet');
foreach($oStylesheets as $oStylesheet)
{
$sStylesheetId = $oStylesheet->getAttribute('id');
$aThemeParameters['stylesheets'][$sStylesheetId] = $oStylesheet->GetText();
}
$compiled_json_sig = ThemeHandler::ComputeSignature($aThemeParameters, array(APPROOT.'datamodels'));
echo " current signature: $compiled_json_sig\n";
$this->assertEquals($precompiledSig, $compiled_json_sig, "Precompiled signature does not match currently compiled one on theme '".$sThemeId."' (cf precompiledsheet $sPrecompiledStylesheet / datamodel $xmlDataCusto)");
}
$aIncludedUrls = ThemeHandler::GetAllUrlFromScss(array('attr' => "123"),APPROOT.$sScssFile);
$this->assertEquals(array('version1'), array_values($aIncludedUrls['aMissingVariables']));
$this->assertEquals(array("approot-relative" => "../../../../../", "version" => "aaa", "attr"=>"123"),
$aIncludedUrls['aFoundVariables']);
$expected_array = array(
'css/ui-lightness/images/tutu.jpg',
"css/ui-lightness/images/tata.jpeg",
'abc/../../../../../css/ui-lightness/images/toutou.png?v=aaa',
"../../../../../css/ui-lightness/images/toto.png?v=aaa",
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7?v=aaa",
"css/ui-lightness/images/tete.jpeg?g=123"
);
$aIncludedUrls['aCompleteUrls'];
$this->assertEquals($expected_array, array_values($aIncludedUrls['aCompleteUrls']));
$this->assertEquals(array('$approot-relative + \'css/ui-lightness/images/titi.gif?v=\' + $version1'), array_values($aIncludedUrls['aToCompleteUrls']));
}
public function providePrecompiledStyleSheets()
/**
* @return array
*/
public function GetAllUrlFromScssProvider()
{
$datamodelfiles=glob(dirname(__FILE__)."/../../datamodels/2.x/**/datamodel*.xml");
$test_set = array();
foreach ($datamodelfiles as $datamodelfile)
{
if (is_file($datamodelfile) &&
$datamodelfile=="/var/www/html/iTop/test/application/../../datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml")
{
$content=file_get_contents($datamodelfile);
if (strpos($content, "precompiled_stylesheet")!==false)
{
$test_set[$datamodelfile]=array($datamodelfile);
}
}
}
return $test_set;
return array('test-getimages.scss' => array('test/application/theme-handler/getimages/test-getimages.scss'));
}
/**
* @param $sUrlTemplate
* @param $aFoundVariables
* @param $sExpectedUrl
*
* @dataProvider ResolveUrlProvider
*/
public function testResolveUrl($sUrlTemplate, $aFoundVariables, $sExpectedUrl)
{
$this->assertEquals($sExpectedUrl, ThemeHandler::ResolveUrl($sUrlTemplate, $aFoundVariables));
}
public function ResolveUrlProvider()
{
return array(
'XXX + $key1 UNresolved' => array("abc/'+ \$key1", array('key'=>'123'), false),
'$key1 + XXX UNresolved' => array("\$key1 + abs", array('key'=>'123'), false),
'XXX + $key UNresolved' => array("abc/'+ \$unknownkey", array('key'=>'123'), false),
'XXX + $key resolved' => array("abc/'+ \$key", array('key'=>'123'), "abc/123"),
'XXX + $key1 resolved' => array("abc/'+ \$key1", array('key1'=>'123'), "abc/123"),
'$key + XXX resolved' => array("\$key + \"/abc", array('key'=>'123'), "123/abc"),
'XXX + $key + YYY resolved' => array("abc/'+ \$key + '/def", array('key'=>'123'), "abc/123/def"),
);
}
public function testGetIncludedImages()
{
$aStylesheetFile=glob($this->tmpDir."/branding/css/*.scss");
$aStylesheetFile[]=$this->tmpDir."/branding/css/ui-lightness/jqueryui.scss";
$expectJsonFilePath = APPROOT.'test/application/theme-handler/expected/themes/basque-red/theme-parameters.json';
$expectedThemeParamJson = file_get_contents($expectJsonFilePath);
$aThemeParametersVariables = json_decode($expectedThemeParamJson, true);
$aIncludedImages = ThemeHandler::GetIncludedImages($aThemeParametersVariables['variables'], $aStylesheetFile, "RELATIVEPATH");
$aExpectedImages = json_decode(file_get_contents(APPROOT.'test/application/theme-handler/getimages/expected-getimages.json'), true);
$this->assertEquals($aExpectedImages, $aIncludedImages);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

View File

@@ -1,6 +1,6 @@
/*
=== SIGNATURE BEGIN ===
{"variables":"37c31105548fce44fecca5cb34e455c9","stylesheets":{"css-variables":"934888ebb4991d4c76555be6b6d1d5cc","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea"},"imports":[]}
{"variables":"37c31105548fce44fecca5cb34e455c9","stylesheets":{"css-variables":"934888ebb4991d4c76555be6b6d1d5cc","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea"},"imports":[],"images":[]}
=== SIGNATURE END ===
*/
====CSSCOMPILEDCONTENT====

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,42 @@
[
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png",
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-icons_ffffff_256x240.png",
"RELATIVEPATH/../../../../../images/actions_right.png",
"RELATIVEPATH/../../../../../images/ac-background.gif",
"RELATIVEPATH/../../../../../images/green-square.gif",
"RELATIVEPATH/../../../../../images/tv-item.gif",
"RELATIVEPATH/../../../../../images/tv-collapsable.gif",
"RELATIVEPATH/../../../../../images/tv-expandable.gif",
"RELATIVEPATH/../../../../../images/tv-item-last.gif",
"RELATIVEPATH/../../../../../images/tv-collapsable-last.gif",
"RELATIVEPATH/../../../../../images/tv-expandable-last.gif",
"RELATIVEPATH/../../../../../images/red-header.gif",
"RELATIVEPATH/../../../../../images/green-header.gif",
"RELATIVEPATH/../../../../../images/orange-header.gif",
"RELATIVEPATH/../../../../../images/calendar.png",
"RELATIVEPATH/../../../../../images/truncated.png",
"RELATIVEPATH/../../../../../images/itop-logo-2.png",
"RELATIVEPATH/../../../../../images/splitter-bkg.png",
"RELATIVEPATH/../../../../../images/plus.gif",
"RELATIVEPATH/../../../../../images/minus.gif",
"RELATIVEPATH/../../../../../images/full-screen.png",
"RELATIVEPATH/../../../../../images/indicator.gif",
"RELATIVEPATH/../../../../../images/delete.png",
"RELATIVEPATH/../../../../../images/bg.gif",
"RELATIVEPATH/../../../../../images/desc.gif",
"RELATIVEPATH/../../../../../images/asc.gif",
"RELATIVEPATH/../../../../../images/info-mini.png",
"RELATIVEPATH/../../../../../images/ok.png",
"RELATIVEPATH/../../../../../images/error.png",
"RELATIVEPATH/../../../../../images/eye-open-555.png",
"RELATIVEPATH/../../../../../images/eye-closed-555.png",
"RELATIVEPATH/../../../../../images/eye-open-fff.png",
"RELATIVEPATH/../../../../../images/eye-closed-fff.png",
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-icons_222222_256x240.png",
"RELATIVEPATH/../../../../../images/breadcrumb-separator.png",
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-icons_E87C1E_256x240.png",
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-icons_1c94c4_256x240.png",
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-icons_F26522_256x240.png",
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png",
"RELATIVEPATH/../../../../../css/ui-lightness/images/ui-icons_ffd27a_256x240.png"
]

View File

@@ -0,0 +1,9 @@
$approot-relative: "../../../../../"
$version:"aaa"
background-image: url('abc/'+ $approot-relative + "css/ui-lightness/images/toutou.png?v=" + $version);
background-image: url($approot-relative + "css/ui-lightness/images/toto.png?v=" + $version);
background: #666666 url($approot-relative + 'css/ui-lightness/images/titi.gif?v=' + $version1) 50% 50% repeat;
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7?v=" + $version);
background-image: url('css/ui-lightness/images/tutu.jpg');
background-image: url("css/ui-lightness/images/tata.jpeg");
background-image: url("css/ui-lightness/images/tete.jpeg?g=" + $attr);