Merge pull request #154 from Combodo/revert-136-feature/fast-theme-compilation

Revert "Faster compilation of themes"
This commit is contained in:
odain-cbd
2020-08-07 16:20:38 +02:00
committed by GitHub
97 changed files with 39 additions and 17718 deletions

View File

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

View File

@@ -89,6 +89,26 @@ 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,9 +25,6 @@
*/
class ThemeHandler
{
const IMAGE_EXTENSIONS = array('png', 'gif', 'jpg', 'jpeg');
private static $oCompileCSSService;
/**
* Return default theme name and parameters
*
@@ -111,7 +108,7 @@ class ThemeHandler
SetupUtils::builddir($sDefaultThemeDirPath);
}
static::CompileTheme($sThemeId, false, $aDefaultTheme['parameters']);
static::CompileTheme($sThemeId, $aDefaultTheme['parameters']);
}
// Return absolute url to theme compiled css
@@ -119,29 +116,17 @@ class ThemeHandler
}
/**
* Compile the $sThemeId theme, the actual compilation is skipped when either
* 1) The produced CSS file exists and is more recent than any of its components (imports, stylesheets)
* 2) The produced CSS file exists and its signature is equal to the expected signature (imports, stylesheets, variables)
* Compile the $sThemeId theme
*
* @param string $sThemeId
* @param mix $sSetupCompilationTimestamp : when setup context this is compilation timestamp. otherwise false.
* @param array|null $aThemeParameters Parameters (variables, imports, stylesheets) for the theme, if not passed, will be retrieved from compiled DM
* @param array|null $aImportsPaths Paths where imports can be found. Must end with '/'
* @param string|null $sWorkingPath Path of the folder used during compilation. Must end with a '/'
*
* @throws \CoreException
*/
public static function CompileTheme($sThemeId, $sSetupCompilationTimestamp=false, $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null)
public static function CompileTheme($sThemeId, $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null)
{
if ($sSetupCompilationTimestamp===false)
{
$sSetupCompilationTimestamp = microtime(true);
$bSetup = false;
}
else
{
$bSetup = true;
}
// Default working path
if($sWorkingPath === null)
{
@@ -163,7 +148,6 @@ class ThemeHandler
// Save parameters if passed... (typically during DM compilation)
if(is_array($aThemeParameters))
{
$aThemeParameters = self::PrepareThemeParameterBeforeSavingAndCompiling($aThemeParameters, $sWorkingPath, $sThemeFolderPath, $sSetupCompilationTimestamp);
file_put_contents($sThemeFolderPath.'/theme-parameters.json', json_encode($aThemeParameters));
}
// ... Otherwise, retrieve them from compiled DM (typically when switching current theme in the config. file)
@@ -180,626 +164,27 @@ 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;
$iImportLastModified = @filemtime($sWorkingPath.$sImport);
$iStyleLastModified = $iStyleLastModified < $iImportLastModified ? $iImportLastModified : $iStyleLastModified;
}
foreach ($aThemeParameters['stylesheets'] as $sStylesheet)
{
$sTmpThemeScssContent .= '@import "'.$sStylesheet.'";'."\n";
$sFile = static::FindStylesheetFile($sStylesheet, $aImportsPaths);
$iStylesheetLastModified = @filemtime($sFile);
$aStylesheetFiles[] = $sFile;
$iStylesheetLastModified = @filemtime($sWorkingPath.$sStylesheet);
$iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified;
}
$aIncludedImages=static::GetIncludedImages($aThemeParameters['variables'], $aStylesheetFiles, $sThemeFolderPath, $aImportsPaths);
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);
$bVarSignatureChanged=false;
if ($bFileExists && $bSetup)
if (!file_exists($sThemeCssPath) || (is_writable($sThemeFolderPath) && (@filemtime($sThemeCssPath) < $iStyleLastModified)))
{
$sPrecompiledSignature = static::GetSignature($sThemeCssPath);
//check variable signature has changed which is independant from any file modification
if (!empty($sPrecompiledSignature)){
$sPreviousVariableSignature = static::GetVarSignature($sPrecompiledSignature);
$sCurrentVariableSignature = md5(json_encode($aThemeParameters['variables']));
$bVarSignatureChanged= ($sPreviousVariableSignature!==$sCurrentVariableSignature);
}
$sTmpThemeCssContent = utils::CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths, $aThemeParameters['variables']);
file_put_contents($sThemeCssPath, $sTmpThemeCssContent);
}
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, $aIncludedImages);
if ($bFileExists && !$bSetup)
{
$sPrecompiledSignature = static::GetSignature($sThemeCssPath);
}
if (!empty($sPrecompiledSignature) && $sActualSignature == $sPrecompiledSignature)
{
touch($sThemeCssPath); // Stylesheet is up to date, mark it as more recent to speedup next time
}
else
{
// Alas, we really need to recompile
// Add the signature to the generated CSS file so that the file can be used as a precompiled stylesheet if needed
$sSignatureComment =
<<<CSS
/*
=== SIGNATURE BEGIN ===
$sActualSignature
=== SIGNATURE END ===
*/
CSS;
if (!static::$oCompileCSSService)
{
static::$oCompileCSSService = new CompileCSSService();
}
//store it again to change $version with latest compiled time
$aThemeParameters = self::PrepareThemeParameterBeforeSavingAndCompiling($aThemeParameters, $sWorkingPath, $sThemeFolderPath, $sSetupCompilationTimestamp);
$sTmpThemeCssContent = static::$oCompileCSSService->CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths,
$aThemeParameters['variables']);
file_put_contents($sThemeFolderPath.'/theme-parameters.json', json_encode($aThemeParameters));
file_put_contents($sThemeCssPath, $sSignatureComment.$sTmpThemeCssContent);
}
}
}
/**
* Compute the signature of a theme defined by its theme parameters. The signature is a JSON structure of
* 1) one MD5 of all the variables/values (JSON encoded)
* 2) the MD5 of each stylesheet file
* 3) the MD5 of each import file
*
* @param string[] $aThemeParameters
* @param string[] $aImportsPaths
* @param string[] $aIncludedImages
*
* @return string
* @throws \Exception
*/
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)
{
$sFile = static::FindStylesheetFile($sImport, $aImportsPaths);
$aSignature['stylesheets'][$key] = md5_file($sFile);
}
foreach ($aThemeParameters['stylesheets'] as $key => $sStylesheet)
{
$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, $aImportsPaths)
{
$aCompleteUrls = array();
$aToCompleteUrls = array();
$aMissingVariables = array();
$aFoundVariables = array('version'=>'');
$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();
$aAllImagesMap = false;
foreach ($aMap ['aCompleteUrls'] as $sUri => $sUrl)
{
$sImg = $sUrl;
if (preg_match("/(.*)\?/", $sUrl, $aMatches))
{
$sImg=$aMatches[1];
}
if (static::HasImageExtension($sImg)
&& ! array_key_exists($sImg, $aImages))
{
$sFilePath = realpath($sImg);
if ($sFilePath!==false)
{
$aImages[$sImg]=$sFilePath;
continue;
}
$sFilePath=realpath($sThemeFolderPath.DIRECTORY_SEPARATOR.$sImg);
if ($sFilePath!==false)
{
$aImages[$sImg]=$sFilePath;
continue;
}
if ($aAllImagesMap===false)
{
//$aAllImagesMap = static::GetAllImagesMap($aImportsPaths);
$aAllImagesMap = static::ScanFolderImages(APPROOT);
}
$sFileName = basename($sImg);
if (array_key_exists($sFileName, $aAllImagesMap))
{
foreach ($aAllImagesMap[$sFileName] as $sFilePath)
{
echo "=========== \n $sUri \n => $sFilePath\n".strpos($sFilePath, $sUri)."\n";
if (strpos($sFilePath, $sUri)!==false)
{
$aImages[$sImg]=$sFilePath;
break;
}
}
}
echo "!!!!!!!!!!!\n";
}
}
return array_values($aImages);
}
public static function GetAllImagesMap($aImportsPaths)
{
$aImagesMap = [];
foreach ($aImportsPaths as $sFolder)
{
if (is_dir($sFolder))
{
$aImagesMap = array_merge($aImagesMap, static::ScanFolderImages($sFolder));
}
}
return $aImagesMap;
}
public static function ScanFolderImages($sFolder)
{
//var_dump($sFolder);
$aImagesMap = [];
foreach (scandir($sFolder) as $sFile)
{
if (in_array($sFile,array(".","..")))
{
continue;
}
$sFilePath = $sFolder . DIRECTORY_SEPARATOR . $sFile;
if (is_dir($sFilePath))
{
$aImagesMap = array_merge($aImagesMap, static::ScanFolderImages($sFilePath));
}
else if (static::HasImageExtension($sFilePath))
{
if (array_key_exists($sFile, $aImagesMap))
{
$aImagesMap[$sFile][] = $sFilePath;
}
else
{
$aImagesMap[$sFile] = [ $sFilePath ] ;
}
}
}
return $aImagesMap;
}
/**
* 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, true);
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
* @param bool $bForceEmptyValueWhenNotFound
*
* @return array
*/
public static function FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent, $bForceEmptyValueWhenNotFound=false)
{
$aNewMissingVars = array();
if (!empty($aMissingVariables))
{
foreach ($aMissingVariables as $var)
{
if (array_key_exists($var, $aThemeParametersVariables))
{
$aFoundVariables[$var] = $aThemeParametersVariables[$var];
}
else
{
if (preg_match_all("/\\\$$var\s*:\s*[\"']{0,1}(.*)[\"']{0,1};/", $sContent, $aValues))
{
$sValue = $aValues[1][0];
if (preg_match_all("/([^!]+)!/", $sValue, $aSubValues))
{
$sValue = trim($aSubValues[1][0], ' "\'');
}
if (strpos($sValue, '$') === false)
{
$aFoundVariables[$var] = $sValue;
}
else{
$aNewMissingVars[] = $var;
}
}
else
{
if ($bForceEmptyValueWhenNotFound)
{
$aFoundVariables[$var] = '';
}
else
{
$aNewMissingVars[] = $var;
}
}
}
}
}
return array($aNewMissingVars, $aFoundVariables);
}
/**
* @param $aFoundVariables
* @param array $aToCompleteUrls
* @param array $aCompleteUrls
*
* @return array
*/
public static function ResolveUrls($aFoundVariables, array $aToCompleteUrls, array $aCompleteUrls)
{
if (!empty($aFoundVariables))
{
$aFoundVariablesWithEmptyValue = [];
foreach ($aFoundVariables as $aFoundVariable => $sValue)
{
$aFoundVariablesWithEmptyValue[$aFoundVariable] = '';
}
foreach ($aToCompleteUrls as $sUrlTemplate)
{
unset($aToCompleteUrls[$sUrlTemplate]);
$sResolvedUrl = static::ResolveUrl($sUrlTemplate, $aFoundVariables);
if ($sResolvedUrl == false)
{
$aToCompleteUrls[$sUrlTemplate] = $sUrlTemplate;
}
else
{
$sUri = static::ResolveUrl($sUrlTemplate, $aFoundVariablesWithEmptyValue);
$aExplodedUri = explode('?', $sUri);
if (empty($aExplodedUri))
{
$aCompleteUrls[$sUri] = $sResolvedUrl;
}
else
{
$aCompleteUrls[$aExplodedUri[0]] = $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
*
* Note the signature can be place anywhere in the CSS file (obviously inside a CSS comment !) but the
* function will be faster if the signature is at the beginning of the file (since the file is scanned from the start)
*
* @param $sFilepath
*
* @return string
*/
public static function GetSignature($sFilepath)
{
$sPreviousLine = '';
$hFile = @fopen($sFilepath, "r");
if ($hFile !== false)
{
$sLine = '';
do
{
$sPreviousLine = $sLine;
$sLine = rtrim(fgets($hFile)); // Remove the trailing \n
}
while (($sLine !== false) && ($sLine != '=== SIGNATURE END ==='));
fclose($hFile);
}
return $sPreviousLine;
}
public static function GetVarSignature($JsonSignature)
{
$aJsonArray = json_decode($JsonSignature, true);
if (array_key_exists('variables', $aJsonArray))
{
return $aJsonArray['variables'];
}
return false;
}
/**
* Find the given file in the list of ImportsPaths directory
* @param string $sFile
* @param string[] $aImportsPaths
* @throws Exception
* @return string
*/
public static function FindStylesheetFile($sFile, $aImportsPaths)
{
foreach($aImportsPaths as $sPath)
{
$sImportedFile = realpath($sPath.'/'.$sFile);
if (file_exists($sImportedFile))
{
return $sImportedFile;
}
}
return ''; // Not found, fail silently, maybe the SCSS compiler knowns better...
}
public static function MockCompileCSSService($oCompileCSSServiceMock)
{
static::$oCompileCSSService = $oCompileCSSServiceMock;
}
/**
* main work is to store compilation timestamp as $version variable
* that way each time there is compilation, images and others will be loaded again on client browser side (apart from cache)
* @param $aThemeParameters
* @param $sWorkingPath
* @param $sThemeFolderPath
*
* @return mixed
*/
public static function PrepareThemeParameterBeforeSavingAndCompiling($aThemeParameters, $sWorkingPath, $sThemeFolderPath, $bSetupCompilationTimestamp)
{
if (array_key_exists('variables', $aThemeParameters))
{
if (is_array($aThemeParameters['variables']))
{
$aThemeParameters['variables']['$version'] = $bSetupCompilationTimestamp;
}
}
else
{
$aThemeParameters['variables']['$version'] = $bSetupCompilationTimestamp;
}
if (!is_dir($sThemeFolderPath))
{
mkdir($sWorkingPath.'/branding/');
mkdir($sWorkingPath.'/branding/themes/');
}
return $aThemeParameters;
}
}
class CompileCSSService
{
/**
* CompileCSSService constructor.
*/
public function __construct()
{
}
public function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array()){
return utils::CompileCSSFromSASS($sSassContent, $aImportPaths, $aVariables);
}
}

View File

@@ -1860,13 +1860,17 @@ 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)
{
return static::GetCacheBusterTimestamp();
$aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
}
/**

View File

@@ -16,7 +16,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.8.0";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css

View File

@@ -8384,7 +8384,6 @@
<stylesheet id="jqueryui">../css/ui-lightness/jqueryui.scss</stylesheet>
<stylesheet id="main">../css/light-grey.scss</stylesheet>
</stylesheets>
<precompiled_stylesheet>itop-config-mgmt/precompiled-themes/light-grey/main.css</precompiled_stylesheet>
</theme>
<theme id="test-red" _delta="define">
<variables>
@@ -8400,7 +8399,6 @@
<stylesheet id="main">../css/light-grey.scss</stylesheet>
<stylesheet id="environment-banner">../css/backoffice-environment-banner.scss</stylesheet>
</stylesheets>
<precompiled_stylesheet>itop-config-mgmt/precompiled-themes/test-red/main.css</precompiled_stylesheet>
</theme>
</themes>
</branding>

View File

@@ -2671,7 +2671,6 @@ EOF;
'variables' => array(),
'imports' => array(),
'stylesheets' => array(),
'precompiled_stylesheet' => '',
);
/** @var \DOMNodeList $oVariables */
@@ -2697,7 +2696,7 @@ EOF;
$sStylesheetId = $oStylesheet->getAttribute('id');
$aThemeParameters['stylesheets'][$sStylesheetId] = $oStylesheet->GetText();
}
$aThemeParameters['precompiled_stylesheet'] = $oTheme->GetChildText('precompiled_stylesheet', '');
$aThemes[$sThemeId] = $aThemeParameters;
}
@@ -2709,7 +2708,6 @@ EOF;
}
// Compile themes
$fStart = microtime(true);
foreach($aThemes as $sThemeId => $aThemeParameters)
{
$sThemeDir = $sThemesDir.$sThemeId;
@@ -2717,21 +2715,10 @@ EOF;
{
SetupUtils::builddir($sThemeDir);
}
// Check if a precompiled version of the theme is supplied
$sPrecompiledFile = $sTempTargetDir.$aThemeParameters['precompiled_stylesheet'];
if (file_exists($sPrecompiledFile))
{
copy($sPrecompiledFile, $sThemeDir.'/main.css');
// Make sure that the copy of the precompiled file is older than any other files to force a validation of the signature
touch($sThemeDir.'/main.css', 1577836800 /* 2020-01-01 00:00:00 */);
}
else if ($sPrecompiledFile != '')
{
$this->Log("Precompiled file not found: '$sPrecompiledFile'");
}
ThemeHandler::CompileTheme($sThemeId, true, $aThemeParameters, $aImportsPaths, $sTempTargetDir);
ThemeHandler::CompileTheme($sThemeId, $aThemeParameters, $aImportsPaths, $sTempTargetDir);
}
$this->Log(sprintf('Themes compilation took: %.3f ms for %d themes.', (microtime(true) - $fStart)*1000.0, count($aThemes)));
}
/**

View File

@@ -1,596 +0,0 @@
<?php
use Combodo\iTop\Test\UnitTest\ItopTestCase;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
* @covers utils
*/
class ThemeHandlerTest extends ItopTestCase
{
const PATTERN = '|\\\/var[^"]+testimages|';
private $compileCSSServiceMock;
private $cssPath;
private $jsonThemeParamFile;
private $tmpDir;
private $aDirsToCleanup=array();
public function setUp()
{
parent::setUp();
require_once(APPROOT.'application/themehandler.class.inc.php');
require_once(APPROOT.'setup/modelfactory.class.inc.php');
$this->compileCSSServiceMock = $this->createMock('CompileCSSService');
ThemeHandler::mockCompileCSSService($this->compileCSSServiceMock);
$this->tmpDir=$this->tmpdir();
$aDirsToCleanup[] = $this->tmpDir;
$this->recurseMkdir($this->tmpDir."/branding/themes/basque-red");
$this->cssPath = $this->tmpDir . '/branding/themes/basque-red/main.css';
$this->jsonThemeParamFile = $this->tmpDir . '/branding/themes/basque-red/theme-parameters.json';
$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))
{
unlink($tmpfile);
}
mkdir($tmpfile);
if (is_dir($tmpfile))
{
return $tmpfile;
}
return sys_get_temp_dir();
}
public function recurse_copy($src,$dst) {
$dir = opendir($src);
@mkdir($dst);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
if ( is_dir($src . '/' . $file) ) {
$this->recurse_copy($src . '/' . $file,$dst . '/' . $file);
}
else {
copy($src . '/' . $file,$dst . '/' . $file);
}
}
}
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 (!$this->recurseMkdir($sThemeFolderPath))
{
$this->assertTrue(false, "Cannot create directory $sThemeFolderPath");
}
$aIncludedImages=ThemeHandler::GetIncludedImages($aThemeParameters['variables'], $aStylesheetFiles, $sThemeFolderPath, $aImportsPaths);
$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)");
}
}
function recurseMkdir($dir)
{
if (is_dir($dir))
{
return true;
}
$sParentDir = dirname($dir);
if (!$this->recurseMkdir($sParentDir))
{
return false;
}
return @mkdir($dir);
}
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":"1d4b4ae2a6fba3db101f8dd1cecab082","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea"},"imports":[],"images":[]}
JSON;
$this->assertEquals($expect_sig,$sig);
}
public function testGetVarSignature()
{
$sig=<<<JSON
{"variables":"37c31105548fce44fecca5cb34e455c9","stylesheets":{"css-variables":"934888ebb4991d4c76555be6b6d1d5cc","jqueryui":"78cfafc3524dac98e61fc2460918d4e5","main":"52d8a7c5530ceb3a4d777364fa4e1eea"},"imports":[]}
JSON;
$var_sig = ThemeHandler::GetVarSignature($sig);
$this->assertEquals("37c31105548fce44fecca5cb34e455c9",$var_sig);
}
/**
* @param bool $readFromParamAttributeFromJson
*
* @throws \CoreException
* @dataProvider CompileThemesProviderWithoutCss
*/
public function testCompileThemeWithoutCssFile_FocusOnParamAttribute($readFromParamAttributeFromJson=false)
{
$expectJsonFilePath = APPROOT.'test/application/theme-handler/expected/themes/basque-red/theme-parameters.json';
$expectedThemeParamJson = file_get_contents($expectJsonFilePath);
$aThemeParameters = json_decode($expectedThemeParamJson, true);
if (is_file($this->jsonThemeParamFile))
{
unlink($this->jsonThemeParamFile);
}
if (is_file($this->cssPath))
{
unlink($this->cssPath);
}
$this->compileCSSServiceMock->expects($this->exactly(1))
->method("CompileCSSFromSASS")
->willReturn("====CSSCOMPILEDCONTENT====");
if($readFromParamAttributeFromJson)
{
copy($expectJsonFilePath, $this->jsonThemeParamFile);
ThemeHandler::CompileTheme('basque-red', true, null, array($this->tmpDir.'/branding/themes/'), $this->tmpDir);
}
else
{
ThemeHandler::CompileTheme('basque-red', true, $aThemeParameters, array($this->tmpDir.'/branding/themes/'), $this->tmpDir);
}
$this->assertTrue(is_file($this->cssPath));
$this->assertEquals($expectedThemeParamJson, file_get_contents($this->jsonThemeParamFile));
$this->assertEquals(file_get_contents(APPROOT . 'test/application/theme-handler/expected/themes/basque-red/main.css'), file_get_contents($this->cssPath));
}
public function CompileThemesProviderWithoutCss()
{
return array(
"pass ParamAttributes and Save them in Json" => array(false),
"use them from saved json" => array(true)
);
}
/**
* @param $ThemeParametersJson
*
* @param int $CompileCount
*
* @throws \CoreException
* @dataProvider CompileThemesProviderEmptyArray
*/
public function testCompileThemesEmptyArray($ThemeParametersJson, $CompileCount=0)
{
$cssPath = $this->tmpDir . '/branding/themes/basque-red/main.css';
copy(APPROOT . 'test/application/theme-handler/expected/themes/basque-red/main.css', $cssPath);
$this->compileCSSServiceMock->expects($this->exactly($CompileCount))
->method("CompileCSSFromSASS")
->willReturn("====CSSCOMPILEDCONTENT====");
ThemeHandler::CompileTheme('basque-red', true, json_decode($ThemeParametersJson, true), array($this->tmpDir.'/branding/themes/'), $this->tmpDir);
}
public function CompileThemesProviderEmptyArray()
{
$emptyImports = '{"variables":{"brand-primary":"#C53030","hover-background-color":"#F6F6F6","icons-filter":"grayscale(1)","search-form-container-bg-color":"#4A5568"},"imports":[],"stylesheets":{"jqueryui":"..\/css\/ui-lightness\/jqueryui.scss","main":"..\/css\/light-grey.scss"}}';
$emptyStyleSheets='{"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":[]}';
$emptyVars='{"variables":[],"imports":{"css-variables":"..\/css\/css-variables.scss"},"stylesheets":{"jqueryui":"..\/css\/ui-lightness\/jqueryui.scss","main":"..\/css\/light-grey.scss"}}';
return array(
"empty imports" => array($emptyImports),
"empty styles" => array($emptyStyleSheets),
"empty vars" => array($emptyVars, 1),
);
}
/**
* @return array
*/
public function CompileThemesProvider()
{
$sModifiedVariableThemeParameterJson='{"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"}}';
$sInitialThemeParamJson='{"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"}}';
$sImportFilePath = '/branding/css/css-variables.scss';
$sVarChangedMainCssPath="test/application/theme-handler/expected/themes/basque-red/main_varchanged.css";
$sStylesheetMainCssPath="test/application/theme-handler/expected/themes/basque-red/main_stylesheet.css";
$sImageMainCssPath="test/application/theme-handler/expected/themes/basque-red/main_imagemodified.css";
$sImportModifiedMainCssPath="test/application/theme-handler/expected/themes/basque-red/main_importmodified.css";
$sStylesheetFilePath = '/branding/css/light-grey.scss';
$sImageFilePath = 'test/application/theme-handler/copied/testimages/images/green-header.gif';
return array(
"setup context: variables list modified without any file touched" => array($sModifiedVariableThemeParameterJson, 1,false,false,false,$sImportFilePath, $sVarChangedMainCssPath),
"setup context: variables list modified with files touched" => array($sModifiedVariableThemeParameterJson, 1,false,true,false,$sImportFilePath, $sVarChangedMainCssPath, false),
"itop page/theme loading; variables list modified without any file touched" => array($sModifiedVariableThemeParameterJson, 0,false,false,false,$sImportFilePath, $sVarChangedMainCssPath, false),
//imports
"import file missing" => array($sInitialThemeParamJson, 0, true, false, false, $sImportFilePath),
"import file touched" => array($sInitialThemeParamJson, 0, false, true, false, $sImportFilePath),
"import file modified" => array($sInitialThemeParamJson, 1, false, false, true, $sImportFilePath, $sImportModifiedMainCssPath),
//stylesheets
"stylesheets file missing" => array($sInitialThemeParamJson, 0, true, false, false, $sStylesheetFilePath),
"stylesheets file touched" => array($sInitialThemeParamJson, 0, false, true, false, $sStylesheetFilePath),
"stylesheets file modified" => array($sInitialThemeParamJson, 1, false, false, true, $sStylesheetFilePath, $sStylesheetMainCssPath),
//images
"image file missing" => array($sInitialThemeParamJson, 0, true, false, false, $sImageFilePath),
"image file touched" => array($sInitialThemeParamJson, 0, false, true, false, $sImageFilePath),
"image file modified" => array($sInitialThemeParamJson, 1, false, false, true, $sImageFilePath, $sImageMainCssPath),
);
}
/**
* @param $ThemeParametersJson
* @param int $iCompileCSSFromSASSCount
* @param boolean $bMissingFile
* @param boolean $bFilesTouchedRecently
* @param boolean $bFileMd5sumModified
* @param null $sFileToTest
* @param null $sExpectedMainCssPath
* @param bool $bSetup
*
* @throws \CoreException
* @dataProvider CompileThemesProvider
*/
public function testCompileThemes($ThemeParametersJson, $iCompileCSSFromSASSCount, $bMissingFile=false, $bFilesTouchedRecently=false, $bFileMd5sumModified=false, $sFileToTest=null, $sExpectedMainCssPath=null, $bSetup=true)
{
$sAfterReplacementCssVariableMd5sum='';
if (is_file($this->tmpDir.'/'.$sFileToTest))
{
$sFileToTest=$this->tmpDir.'/'.$sFileToTest;
}
else
{
$sFileToTest=APPROOT.'/'.$sFileToTest;
}
//copy images in test dir
$sAbsoluteImagePath = APPROOT .'test/application/theme-handler/copied/testimages/';
$this->recurseMkdir($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";
$sBeforeReplacementCssVariableMd5sum = md5_file($sCssVarPath);
echo 'BEFORE :' . $sBeforeReplacementCssVariableMd5sum .' ' . $sCssVarPath . ' ';
$sCssVariableContent = file_get_contents($sCssVarPath);
$sLine = '$approot-relative: "'.$sAbsoluteImagePath.'" !default;';
$sCssVariableContent=preg_replace("/\\\$approot-relative: \"(.*)\"/", $sLine, $sCssVariableContent);
file_put_contents($sCssVarPath, $sCssVariableContent);
if ($bMissingFile)
{
$sAfterReplacementCssVariableMd5sum = $sBeforeReplacementCssVariableMd5sum;
unlink($sFileToTest);
}
if (is_file($sCssVarPath))
{
$sAfterReplacementCssVariableMd5sum = md5_file($sCssVarPath);
}
//change cssvar md5sum + image absolute paths
$sMainCssContent = file_get_contents(APPROOT."test/application/theme-handler/expected/themes/basque-red/main_testcompilethemes.css");
$sMainCssContent = preg_replace('/MD5SUM/', $sAfterReplacementCssVariableMd5sum, $sMainCssContent);
$sReplacement = rtrim($sAbsoluteImagePath, '/');
$sReplacement=preg_replace('|\/|', '\/', $sReplacement);
$sMainCssContent = preg_replace(static::PATTERN, $sReplacement, $sMainCssContent);
$cssPath = $this->tmpDir . '/branding/themes/basque-red/main.css';
echo 'PUT md5sum: '.$sAfterReplacementCssVariableMd5sum.' in '.$cssPath.' ';
file_put_contents($cssPath, $sMainCssContent);
//should be after main.css modification to make sure precompilation check will be performed
if ($bFilesTouchedRecently)
{
sleep(1);
touch($sFileToTest);
}
//same: it should be after main.css modification
if ($bFileMd5sumModified)
{
$sMd5sum = md5_file($sFileToTest);
echo ' BEFORE touch: ' . $sMd5sum .' ' . $sFileToTest;
sleep(1);
file_put_contents($sFileToTest, "###\n".file_get_contents($sFileToTest));
$sMd5sum = md5_file($sFileToTest);
echo ' AFTER touch: ' . $sMd5sum .' ' . $sFileToTest;
}
if (is_file($sCssVarPath))
{
$sAfterReplacementCssVariableMd5sum = md5_file($sCssVarPath);
}
$this->compileCSSServiceMock->expects($this->exactly($iCompileCSSFromSASSCount))
->method("CompileCSSFromSASS")
->willReturn("====CSSCOMPILEDCONTENT====");
$aThemeParameters = json_decode($ThemeParametersJson, true);
ThemeHandler::CompileTheme('basque-red', $bSetup, $aThemeParameters, array($this->tmpDir.'/branding/themes/'), $this->tmpDir);
if ($iCompileCSSFromSASSCount==1)
{
$sExpectedMainCssFile = APPROOT.$sExpectedMainCssPath;
if (!is_file($sExpectedMainCssFile))
{
$this->assertTrue(false, "Cannot find expected main css file $sExpectedMainCssFile");
}
$aPatterns = array(static::PATTERN, '/'.$sBeforeReplacementCssVariableMd5sum.'/');
$aPatterns[] = "/8100523d2e76a70266f3e7110e2fe5fb/";
$aReplacements = array($sReplacement, $sAfterReplacementCssVariableMd5sum);
$aReplacements[] = md5(json_encode($aThemeParameters['variables']));
var_dump($aReplacements);
$this->DoInnerJsonValidation($sExpectedMainCssFile, $cssPath, $aPatterns, $aReplacements);
}
}
public function DoInnerJsonValidation($sExpectedCssFile, $sActualCssFile, $aPatterns, $aReplacements)
{
$sActualContent = file_get_contents($sActualCssFile);
//replace absolute path to fix it in any envt
$sExpectedContent = preg_replace($aPatterns, $aReplacements, file_get_contents($sExpectedCssFile));
//echo($sExpectedContent);
if ($sExpectedContent != $sActualContent)
{
//try to have inner json diff failure
/** @var array $aExpectedJson */
//replace absolute path to fix it in any envt
$sExpectedJson = preg_replace($aPatterns, $aReplacements, ThemeHandler::GetSignature($sExpectedCssFile));
$aExpectedJson = json_decode($sExpectedJson, true);
/** @var array $aActualJson */
$aActualJson = json_decode(ThemeHandler::GetSignature($sActualCssFile), true);
$this->assertEquals($aExpectedJson, $aActualJson, "CSS file dont match ($sExpectedCssFile / $sActualCssFile)");
}
$this->assertTrue(true);
}
/**
* @param $sScssFile
*
* @dataProvider GetAllUrlFromScssProvider
*/
public function testGetAllUrlFromScss($sScssFile)
{
$aIncludedUrls = ThemeHandler::GetAllUrlFromScss(array('attr' => "123"),APPROOT.$sScssFile);
$this->assertEquals(array('approot-relative', 'version', 'version1'), array_values($aIncludedUrls['aMissingVariables']));
$this->assertEquals(array("attr"=>"123"),
$aIncludedUrls['aFoundVariables']);
$aExpectedCompletedUrls = array(
'css/ui-lightness/images/tutu.jpg',
"css/ui-lightness/images/tata.jpeg",
"css/ui-lightness/images/tete.jpeg?g=123"
);
$aExpectedToCompleteUrls = array(
'\'abc/\'+ $approot-relative + "css/ui-lightness/images/toutou.png?v=" + $version',
"\$approot-relative + \"css/ui-lightness/images/toto.png?v=\" + \$version",
'$approot-relative + \'css/ui-lightness/images/titi.gif?v=\' + $version1',
'"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7?v=" + $version',
);
$aIncludedUrls['aCompleteUrls'];
$this->assertEquals($aExpectedCompletedUrls, array_values($aIncludedUrls['aCompleteUrls']));
$this->assertEquals($aExpectedToCompleteUrls, array_values($aIncludedUrls['aToCompleteUrls']));
}
/**
* @return array
*/
public function GetAllUrlFromScssProvider()
{
return array('test-getimages.scss' => array('test/application/theme-handler/getimages/test-getimages.scss'));
}
public function testFindMissingVariables()
{
$sContent = <<< 'SCSS'
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
$approot-relative2: "../../" !default; // relative to env-***/branding/themes/***/main.css
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$brand-primary: $combodo-orange !default;
$brand-primary-lightest: lighten($brand-primary, 15%) !default;
$content-color: #eeeeee !default;
$default-font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif !default;
$icons-filter: hue-rotate(0deg) !default;
$toto : titi;
SCSS;
$aMissingVariables = array('gabu', 'toto', 'approot-relative', 'approot-relative2', 'gray-base', 'gray-darker', 'brand-primary', 'brand-primary-lightest', 'content-color', 'default-font-family', 'icons-filter');
list($aMissingVariables, $aFoundVariables) = ThemeHandler::FindMissingVariables(array('gabu' => 'zomeu'), $aMissingVariables, array("a" => "b"), $sContent);
$aExpectedFoundVariables = array(
'gabu' => 'zomeu',
'toto' => 'titi',
'approot-relative' => '../../../../../',
'approot-relative2' => '../../',
'gray-base' => '#000',
'a' => 'b',
'content-color' => '#eeeeee',
'default-font-family' => 'Trebuchet MS,Tahoma,Verdana,Arial,sans-serif',
'icons-filter' => 'hue-rotate(0deg)',
'toto' => 'titi',
);
$this->assertEquals($aExpectedFoundVariables, $aFoundVariables);
$this->assertEquals(array('gray-darker', 'brand-primary', 'brand-primary-lightest'), $aMissingVariables);
}
/**
* @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", [APPROOT.'css']);
$aExpectedImages = json_decode(file_get_contents(APPROOT.'test/application/theme-handler/getimages/expected-getimages.json'), true);
$this->assertEquals($aExpectedImages, $aIncludedImages);
}
public function testGetAllImages()
{
$aFolders = [
APPROOT.'test/application/theme-handler/getimages/imagefolder1',
APPROOT.'test/application/theme-handler/getimages/imagefolder2'
];
$aAllImagesMap = ThemeHandler::GetAllImagesMap($aFolders);
$aExpectedImagesMap = [
"titi.jpeg" => [ APPROOT."test/application/theme-handler/getimages/imagefolder1/subdir/titi.jpeg"],
"toto.jpg" => [ APPROOT."test/application/theme-handler/getimages/imagefolder1/toto.jpg"],
"tete.gif" => [ APPROOT."test/application/theme-handler/getimages/imagefolder2/subdir/tete.gif"],
"tutu.png" => [ APPROOT."test/application/theme-handler/getimages/imagefolder2/subdir/tutu.png", APPROOT."test/application/theme-handler/getimages/imagefolder2/tutu.png"],
];
$this->assertEquals($aExpectedImagesMap, $aAllImagesMap);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

View File

@@ -1,123 +0,0 @@
/*!
* Copyright (C) 2013-2020 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: #444 !default;
$gray: #777 !default;
$gray-light: #808080 !default;
$gray-lighter: #ddd !default;
$gray-extra-light: #F1F1F1 !default;
$white: #FFFFFF !default;
$combodo-orange: #EA7D1E !default;
$combodo-dark-gray: #585653 !default;
$combodo-orange-dark: darken($combodo-orange, 13.8%) !default;
$combodo-orange-darker: darken($combodo-orange, 18%) !default;
$combodo-dark-gray-dark: darken($combodo-dark-gray, 13.5%) !default;
$combodo-dark-gray-darker: darken($combodo-dark-gray, 18%) !default;
// Brand colors
// - Bases
$brand-primary: $combodo-orange !default;
$brand-secondary: $combodo-dark-gray !default;
// - Shades
$brand-primary-lightest: lighten($brand-primary, 15%) !default;
$brand-primary-lighter: lighten($brand-primary, 10%) !default;
$brand-primary-light: lighten($brand-primary, 6%) !default;
$brand-primary-dark: darken($brand-primary, 6%) !default;
$brand-primary-darker: darken($brand-primary, 10%) !default;
$brand-primary-darkest: darken($brand-primary, 15%) !default;
$brand-secondary-lightest: lighten($brand-secondary, 15%) !default;
$brand-secondary-lighter: lighten($brand-secondary, 10%) !default;
$brand-secondary-light: lighten($brand-secondary, 6%) !default;
$brand-secondary-dark: darken($brand-secondary, 6%) !default;
$brand-secondary-darker: darken($brand-secondary, 10%) !default;
$brand-secondary-darkest: darken($brand-secondary, 15%) !default;
// Vars
$highlight-color: $brand-primary !default;
$grey-color: #555555 !default;
$complement-color: #1c94c4 !default;
$complement-light: #d6e8ef !default;
$frame-background-color: $gray-extra-light !default;
$text-color: #000 !default;
$box-radius: 0px !default;
$box-shadow-regular: 0 1px 1px rgba(0, 0, 0, 0.15) !default;
$body-background-color : $white !default;
$hyperlink-color: $complement-color !default;
$hyperlink-text-decoration: none !default;
////////////
// Search //
$search-form-container-color: $white !default;
$search-form-container-bg-color: $complement-color !default;
//
$search-criteria-box-color: #2D2D2D !default;
$search-criteria-box-picto-color: $brand-primary !default;
$search-criteria-box-bg-color: #EEEEEE !default;
$search-criteria-box-hover-color: $white !default;
$search-criteria-box-border-color: #CCCCCC !default;
$search-criteria-box-border: 1px solid $search-criteria-box-border-color !default;
$search-criteria-box-radius: 1px !default;
$search-criteria-more-less-details-color: #3F7294 !default;
//
$search-add-criteria-box-color: $search-criteria-box-color !default;
$search-add-criteria-box-bg-color: $white !default;
$search-add-criteria-box-hover-color: $gray-extra-light !default;
//
$search-button-box-color: $brand-primary !default;
$search-button-box-bg-color: $white !default;
$search-button-box-bg-hover-color: $gray-extra-light !default;
/////////////
// Buttons //
/////////////
// Toggle button
$toggle-button-bg-color: #CCCCCC !default;
$toggle-button-bg-checked-color: $brand-primary !default;
$toggle-button-slider-bg-color: #FFFFFF !default;
// Console elements
$summary-details-background: $grey-color !default;
$main-header-background: $frame-background-color !default;
$table-even-background: $frame-background-color !default;
$table-hover-background: #fdf5d0 !default;
$popup-menu-highlight-color: $highlight-color !default;
$popup-menu-text-color: #000 !default;
$popup-menu-background-color: #fff !default;
$popup-menu-text-higlight-color: #fff !default;
$breadcrumb-color: $grey-color !default;
$breadcrumb-highlight-color: $highlight-color !default;
// jQuery UI widgets vars
$primary-text-color: #333333 !default;
$secondary-text-color: $grey-color !default;
$error-text-color: $white !default;
$highlight-text-color: #363636 !default;
$hover-background-color: #fde17c !default;
$border-highlight-color: $brand-primary-dark !default;
$highlight-item-color: $white !default;
$content-color: #eeeeee !default;
$default-font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif !default;
$icons-filter: hue-rotate(0deg) !default;

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

View File

@@ -1,6 +0,0 @@
/*
=== SIGNATURE BEGIN ===
{"variables":"37c31105548fce44fecca5cb34e455c9","stylesheets":{"css-variables":"1d4b4ae2a6fba3db101f8dd1cecab082","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

@@ -1 +0,0 @@
{"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"}}

View File

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

View File

@@ -1,9 +0,0 @@
$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);