diff --git a/application/themehandler.class.inc.php b/application/themehandler.class.inc.php index ab636a5a9..8dbd016ad 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) @@ -196,7 +201,7 @@ class ThemeHandler $iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified; } - $aIncludedImages=static::GetIncludedImages($aThemeParameters['variables'], $aStylesheetFiles, $sThemeFolderPath); + $aIncludedImages=static::GetIncludedImages($aThemeParameters['variables'], $aStylesheetFiles, $sThemeFolderPath, $aImportsPaths); foreach ($aIncludedImages as $sImage) { if (!is_file($sImage)) @@ -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); } } @@ -315,7 +323,7 @@ CSS; * @return array * @since 2.8.0 */ - public static function GetIncludedImages($aThemeParametersVariables, $aStylesheetFiles, $sThemeFolderPath) + public static function GetIncludedImages($aThemeParametersVariables, $aStylesheetFiles, $sThemeFolderPath, $aImportsPaths) { $aCompleteUrls = array(); $aToCompleteUrls = array(); @@ -343,7 +351,8 @@ CSS; $aMap = static::ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFiles); $aImages = array(); - foreach ($aMap ['aCompleteUrls'] as $sUrl) + $aAllImagesMap = false; + foreach ($aMap ['aCompleteUrls'] as $sUri => $sUrl) { $sImg = $sUrl; if (preg_match("/(.*)\?/", $sUrl, $aMatches)) @@ -354,17 +363,92 @@ CSS; if (static::HasImageExtension($sImg) && ! array_key_exists($sImg, $aImages)) { - if (!is_file($sImg)) + $sFilePath = realpath($sImg); + if ($sFilePath!==false) { - $sImg=$sThemeFolderPath.DIRECTORY_SEPARATOR.$sImg; + $aImages[$sImg]=$sFilePath; + continue; } - $aImages[$sImg]=$sImg; + + $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 @@ -466,6 +550,12 @@ CSS; { if (!empty($aFoundVariables)) { + $aFoundVariablesWithEmptyValue = []; + foreach ($aFoundVariables as $aFoundVariable => $sValue) + { + $aFoundVariablesWithEmptyValue[$aFoundVariable] = ''; + } + foreach ($aToCompleteUrls as $sUrlTemplate) { unset($aToCompleteUrls[$sUrlTemplate]); @@ -476,7 +566,16 @@ CSS; } else { - $aCompleteUrls[$sUrlTemplate] = $sResolvedUrl; + $sUri = static::ResolveUrl($sUrlTemplate, $aFoundVariablesWithEmptyValue); + $aExplodedUri = explode('?', $sUri); + if (empty($aExplodedUri)) + { + $aCompleteUrls[$sUri] = $sResolvedUrl; + } + else + { + $aCompleteUrls[$aExplodedUri[0]] = $sResolvedUrl; + } } } } @@ -651,10 +750,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/test/application/ThemeHandlerTest.php b/test/application/ThemeHandlerTest.php index 2ad2789ef..dd2f634ec 100644 --- a/test/application/ThemeHandlerTest.php +++ b/test/application/ThemeHandlerTest.php @@ -164,7 +164,7 @@ class ThemeHandlerTest extends ItopTestCase $this->assertTrue(false, "Cannot create directory $sThemeFolderPath"); } - $aIncludedImages=ThemeHandler::GetIncludedImages($aThemeParameters['variables'], $aStylesheetFiles, $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); @@ -572,9 +572,25 @@ 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"); + $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); + } } \ No newline at end of file diff --git a/test/application/theme-handler/getimages/expected-getimages.json b/test/application/theme-handler/getimages/expected-getimages.json index b4b7321ce..d963afc5b 100644 --- a/test/application/theme-handler/getimages/expected-getimages.json +++ b/test/application/theme-handler/getimages/expected-getimages.json @@ -1,42 +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" + "/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" ] \ No newline at end of file diff --git a/test/application/theme-handler/getimages/imagefolder2/subdir/tutu.png b/test/application/theme-handler/getimages/imagefolder2/subdir/tutu.png new file mode 100644 index 000000000..e69de29bb