icingaweb2/library/Icinga/Web/StyleSheet.php
Eric Lippmann b9c2c45af1 CLI: Fix CSS path
CLI commands which require our LESS parser had no access to our CSS
because the public path was set to the path of the icingacli executable
which is most likely bin.
2019-06-04 09:43:39 +02:00

226 lines
6.3 KiB
PHP

<?php
/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
namespace Icinga\Web;
use Exception;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
use Icinga\Authentication\Auth;
use Icinga\Exception\IcingaException;
/**
* Send CSS for Web 2 and all loaded modules to the client
*/
class StyleSheet
{
/**
* The name of the default theme
*
* @var string
*/
const DEFAULT_THEME = 'Icinga';
/**
* Array of core LESS files Web 2 sends to the client
*
* @var string[]
*/
protected static $lessFiles = array(
'../application/fonts/fontello-ifont/css/ifont-embedded.css',
'css/vendor/normalize.css',
'css/vendor/tipsy.css',
'css/icinga/base.less',
'css/icinga/badges.less',
'css/icinga/mixins.less',
'css/icinga/grid.less',
'css/icinga/nav.less',
'css/icinga/main.less',
'css/icinga/animation.less',
'css/icinga/layout.less',
'css/icinga/layout-structure.less',
'css/icinga/menu.less',
'css/icinga/tabs.less',
'css/icinga/forms.less',
'css/icinga/setup.less',
'css/icinga/widgets.less',
'css/icinga/login.less',
'css/icinga/about.less',
'css/icinga/controls.less',
'css/icinga/dev.less',
'css/icinga/spinner.less',
'css/icinga/compat.less',
'css/icinga/print.less',
'css/icinga/responsive.less'
);
/**
* Application instance
*
* @var \Icinga\Application\EmbeddedWeb
*/
protected $app;
/**
* Less compiler
*
* @var LessCompiler
*/
protected $lessCompiler;
/**
* Path to the public directory
*
* @var string
*/
protected $pubPath;
/**
* Create the StyleSheet
*/
public function __construct()
{
$app = Icinga::app();
$this->app = $app;
$this->lessCompiler = new LessCompiler();
$this->pubPath = $app->getBaseDir('public');
$this->collect();
}
/**
* Collect Web 2 and module LESS files and add them to the LESS compiler
*/
protected function collect()
{
foreach (self::$lessFiles as $lessFile) {
$this->lessCompiler->addLessFile($this->pubPath . '/' . $lessFile);
}
$mm = $this->app->getModuleManager();
foreach ($mm->getLoadedModules() as $moduleName => $module) {
if ($module->hasCss()) {
foreach ($module->getCssFiles() as $lessFilePath) {
$this->lessCompiler->addModuleLessFile($moduleName, $lessFilePath);
}
}
}
$themingConfig = $this->app->getConfig()->getSection('themes');
$defaultTheme = $themingConfig->get('default');
$theme = null;
if ($defaultTheme !== null && $defaultTheme !== self::DEFAULT_THEME) {
$theme = $defaultTheme;
}
if (! (bool) $themingConfig->get('disabled', false)) {
$auth = Auth::getInstance();
if ($auth->isAuthenticated()) {
$userTheme = $auth->getUser()->getPreferences()->getValue('icingaweb', 'theme');
if ($userTheme !== null) {
$theme = $userTheme;
}
}
}
if ($theme && $theme !== self::DEFAULT_THEME) {
if (($pos = strpos($theme, '/')) !== false) {
$moduleName = substr($theme, 0, $pos);
$theme = substr($theme, $pos + 1);
if ($mm->hasLoaded($moduleName)) {
$module = $mm->getModule($moduleName);
$this->lessCompiler->setTheme($module->getCssDir() . '/themes/' . $theme . '.less');
}
} else {
$this->lessCompiler->setTheme($this->pubPath . '/css/themes/' . $theme . '.less');
}
}
}
/**
* Get the stylesheet for PDF export
*
* @return $this
*/
public static function forPdf()
{
$styleSheet = new self();
$styleSheet->lessCompiler->addLessFile($styleSheet->pubPath . '/css/pdf/pdfprint.less');
// TODO(el): Caching
return $styleSheet;
}
/**
* Render the stylesheet
*
* @param bool $minified Whether to compress the stylesheet
*
* @return string CSS
*/
public function render($minified = false)
{
if ($minified) {
$this->lessCompiler->compress();
}
return $this->lessCompiler->render();
}
/**
* Send the stylesheet to the client
*
* Does not cache the stylesheet if the HTTP header Cache-Control or Pragma is set to no-cache.
*
* @param bool $minified Whether to compress the stylesheet
*/
public static function send($minified = false)
{
$styleSheet = new self();
$request = $styleSheet->app->getRequest();
$response = $styleSheet->app->getResponse();
$response->setHeader('Cache-Control', 'public', true);
$noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache';
if (! $noCache && FileCache::etagMatchesFiles($styleSheet->lessCompiler->getLessFiles())) {
$response
->setHttpResponseCode(304)
->sendHeaders();
return;
}
$etag = FileCache::etagForFiles($styleSheet->lessCompiler->getLessFiles());
$response->setHeader('ETag', $etag, true)
->setHeader('Content-Type', 'text/css', true);
$cacheFile = 'icinga-' . $etag . ($minified ? '.min' : '') . '.css';
$cache = FileCache::instance();
if (! $noCache && $cache->has($cacheFile)) {
$response->setBody($cache->get($cacheFile));
} else {
$css = $styleSheet->render($minified);
$response->setBody($css);
$cache->store($cacheFile, $css);
}
$response->sendResponse();
}
/**
* Render the stylesheet
*
* @return string
*/
public function __toString()
{
try {
return $this->render();
} catch (Exception $e) {
Logger::error($e);
return IcingaException::describe($e);
}
}
}