diff --git a/application/themehandler.class.inc.php b/application/themehandler.class.inc.php index ab636a5a9..8d654ae8a 100644 --- a/application/themehandler.class.inc.php +++ b/application/themehandler.class.inc.php @@ -124,15 +124,24 @@ class ThemeHandler * 2) The produced CSS file exists and its signature is equal to the expected signature (imports, stylesheets, variables) * * @param string $sThemeId - * @param bool $bSetup : indicated whether compilation is performed in setup context (true) or when loading a page/theme (false) + * @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, $bSetup=false, $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null) + public static function CompileTheme($sThemeId, $sSetupCompilationTimestamp=false, $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null) { + if ($sSetupCompilationTimestamp===false) + { + $sSetupCompilationTimestamp = microtime(true); + $bSetup = false; + } + else + { + $bSetup = true; + } // Default working path if($sWorkingPath === null) { @@ -154,11 +163,7 @@ class ThemeHandler // Save parameters if passed... (typically during DM compilation) if(is_array($aThemeParameters)) { - if (!is_dir($sThemeFolderPath)) - { - mkdir($sWorkingPath.'/branding/'); - mkdir($sWorkingPath.'/branding/themes/'); - } + $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) @@ -256,8 +261,11 @@ CSS; { 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); } } @@ -357,6 +365,10 @@ CSS; if (!is_file($sImg)) { $sImg=$sThemeFolderPath.DIRECTORY_SEPARATOR.$sImg; + if (!is_file($sImg)) + { + $sImg=preg_replace('#'.DIRECTORY_SEPARATOR.'..#', '', $sImg, 1); + } } $aImages[$sImg]=$sImg; } @@ -651,10 +663,42 @@ CSS; return ''; // Not found, fail silently, maybe the SCSS compiler knowns better... } - public static function mockCompileCSSService($oCompileCSSServiceMock) + 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 diff --git a/application/utils.inc.php b/application/utils.inc.php index 2f7d7edec..25c6b86f4 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -1864,7 +1864,12 @@ class utils */ public static function GetCompiledModuleVersion($sModuleName) { - return static::GetCacheBusterTimestamp(); + $aModulesInfo = GetModulesInfo(); + if (array_key_exists($sModuleName, $aModulesInfo)) + { + return $aModulesInfo[$sModuleName]['version']; + } + return null; } /** diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index cc056e408..ffdef588f 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -60,6 +60,7 @@ class MFCompiler protected $aSnippets; protected $aRelations; protected $sEnvironment; + protected $sCompilationTimeStamp; public function __construct($oModelFactory, $sEnvironment) { @@ -74,7 +75,8 @@ class MFCompiler $this->sMainPHPCode .= " * This file was automatically generated by the compiler on ".date('Y-m-d H:i:s')." -- DO NOT EDIT\n"; $this->sMainPHPCode .= " */\n"; $this->sMainPHPCode .= "\n"; - $this->sMainPHPCode .= "define('COMPILATION_TIMESTAMP', '".microtime(true)."');\n"; + $this->sCompilationTimeStamp = microtime(true); + $this->sMainPHPCode .= "define('COMPILATION_TIMESTAMP', '".$this->sCompilationTimeStamp."');\n"; $this->aSnippets = array(); $this->aRelations = array(); } @@ -96,6 +98,14 @@ class MFCompiler { return $this->aLog; } + + /** + * @return mixed + */ + public function GetCompilationTimeStamp() + { + return $this->sCompilationTimeStamp; + } /** @@ -2759,7 +2769,7 @@ EOF; { $this->Log("Precompiled file not found: '$sPrecompiledFile'"); } - ThemeHandler::CompileTheme($sThemeId, true, $aThemeParameters, $aImportsPaths, $sTempTargetDir); + ThemeHandler::CompileTheme($sThemeId, $this->GetCompilationTimeStamp(), $aThemeParameters, $aImportsPaths, $sTempTargetDir); } $this->Log(sprintf('Themes compilation took: %.3f ms for %d themes.', (microtime(true) - $fStart)*1000.0, count($aThemes))); } diff --git a/test/application/ThemeHandlerTest.php b/test/application/ThemeHandlerTest.php index 2ad2789ef..cd22f4f38 100644 --- a/test/application/ThemeHandlerTest.php +++ b/test/application/ThemeHandlerTest.php @@ -25,7 +25,7 @@ class ThemeHandlerTest extends ItopTestCase require_once(APPROOT.'setup/modelfactory.class.inc.php'); $this->compileCSSServiceMock = $this->createMock('CompileCSSService'); - ThemeHandler::mockCompileCSSService($this->compileCSSServiceMock); + ThemeHandler::MockCompileCSSService($this->compileCSSServiceMock); $this->tmpDir=$this->tmpdir(); $aDirsToCleanup[] = $this->tmpDir; @@ -253,14 +253,15 @@ JSON; ->method("CompileCSSFromSASS") ->willReturn("====CSSCOMPILEDCONTENT===="); + $sSetupCompilationTimestamp = microtime(true); if($readFromParamAttributeFromJson) { copy($expectJsonFilePath, $this->jsonThemeParamFile); - ThemeHandler::CompileTheme('basque-red', true, null, array($this->tmpDir.'/branding/themes/'), $this->tmpDir); + ThemeHandler::CompileTheme('basque-red', $sSetupCompilationTimestamp, null, array($this->tmpDir.'/branding/themes/'), $this->tmpDir); } else { - ThemeHandler::CompileTheme('basque-red', true, $aThemeParameters, array($this->tmpDir.'/branding/themes/'), $this->tmpDir); + ThemeHandler::CompileTheme('basque-red', $sSetupCompilationTimestamp, $aThemeParameters, array($this->tmpDir.'/branding/themes/'), $this->tmpDir); } $this->assertTrue(is_file($this->cssPath)); $this->assertEquals($expectedThemeParamJson, file_get_contents($this->jsonThemeParamFile)); @@ -291,8 +292,8 @@ JSON; $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); + $sSetupCompilationTimestamp = microtime(true); + ThemeHandler::CompileTheme('basque-red', $sSetupCompilationTimestamp, json_decode($ThemeParametersJson, true), array($this->tmpDir.'/branding/themes/'), $this->tmpDir); } public function CompileThemesProviderEmptyArray() @@ -356,6 +357,8 @@ JSON; */ public function testCompileThemes($ThemeParametersJson, $iCompileCSSFromSASSCount, $bMissingFile=false, $bFilesTouchedRecently=false, $bFileMd5sumModified=false, $sFileToTest=null, $sExpectedMainCssPath=null, $bSetup=true) { + $sSetupCompilationTimestamp = ($bSetup) ? microtime(true) : false; + $sAfterReplacementCssVariableMd5sum=''; if (is_file($this->tmpDir.'/'.$sFileToTest)) { @@ -430,7 +433,7 @@ JSON; ->willReturn("====CSSCOMPILEDCONTENT===="); $aThemeParameters = json_decode($ThemeParametersJson, true); - ThemeHandler::CompileTheme('basque-red', $bSetup, $aThemeParameters, array($this->tmpDir.'/branding/themes/'), $this->tmpDir); + ThemeHandler::CompileTheme('basque-red', $sSetupCompilationTimestamp, $aThemeParameters, array($this->tmpDir.'/branding/themes/'), $this->tmpDir); if ($iCompileCSSFromSASSCount==1) { @@ -576,5 +579,10 @@ SCSS; $aExpectedImages = json_decode(file_get_contents(APPROOT.'test/application/theme-handler/getimages/expected-getimages.json'), true); $this->assertEquals($aExpectedImages, $aIncludedImages); + foreach ($aIncludedImages as $sImagePath) + { + $sFilename = str_replace('RELATIVEPATH', dirname($this->cssPath), $sImagePath); + $this->assertTrue(is_file($sFilename), "Image $sFilename does not exist"); + } } } \ No newline at end of file