2016-11-08 15:36:35 +03:00
< ? php
/**
* @ copyright Copyright ( c ) 2016 , John Molakvoæ ( skjnldsv @ protonmail . com )
*
2020-03-31 11:49:10 +03:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2017-11-06 17:56:42 +03:00
* @ author John Molakvoæ ( skjnldsv ) < skjnldsv @ protonmail . com >
* @ author Julius Haertl < jus @ bitgrid . net >
* @ author Julius Härtl < jus @ bitgrid . net >
* @ author Morris Jobke < hey @ morrisjobke . de >
2019-12-03 21:57:53 +03:00
* @ author Robin Appelman < robin @ icewind . nl >
2017-11-06 17:56:42 +03:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2019-12-03 21:57:53 +03:00
* @ author Roland Tapken < roland @ bitarbeiter . net >
2017-11-06 17:56:42 +03:00
*
2016-11-08 15:36:35 +03:00
* @ license GNU AGPL version 3 or any later version
*
* This program 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 .
*
* This program 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
2019-12-03 21:57:53 +03:00
* along with this program . If not , see < http :// www . gnu . org / licenses />.
2016-11-08 15:36:35 +03:00
*
*/
namespace OC\Template ;
2020-10-15 16:59:21 +03:00
use OC\AppConfig ;
2019-11-22 22:52:10 +03:00
use OC\Files\AppData\Factory ;
2019-05-29 17:09:07 +03:00
use OC\Memcache\NullCache ;
2018-07-24 18:30:39 +03:00
use OCP\AppFramework\Utility\ITimeFactory ;
2016-11-30 18:30:04 +03:00
use OCP\Files\IAppData ;
2016-11-09 13:18:43 +03:00
use OCP\Files\NotFoundException ;
2017-03-20 14:31:29 +03:00
use OCP\Files\NotPermittedException ;
2017-02-17 18:42:07 +03:00
use OCP\Files\SimpleFS\ISimpleFile ;
2016-11-30 23:51:09 +03:00
use OCP\Files\SimpleFS\ISimpleFolder ;
2017-03-21 23:53:20 +03:00
use OCP\ICache ;
2018-03-07 16:13:36 +03:00
use OCP\ICacheFactory ;
2017-03-06 01:28:59 +03:00
use OCP\IConfig ;
2016-11-30 18:30:04 +03:00
use OCP\ILogger ;
2019-05-29 17:09:07 +03:00
use OCP\IMemcache ;
2016-11-30 18:30:04 +03:00
use OCP\IURLGenerator ;
2020-01-23 16:52:16 +03:00
use ScssPhp\ScssPhp\Compiler ;
use ScssPhp\ScssPhp\Exception\ParserException ;
use ScssPhp\ScssPhp\Formatter\Crunched ;
use ScssPhp\ScssPhp\Formatter\Expanded ;
2016-11-08 15:36:35 +03:00
class SCSSCacher {
2016-11-30 18:30:04 +03:00
/** @var ILogger */
protected $logger ;
/** @var IAppData */
protected $appData ;
/** @var IURLGenerator */
protected $urlGenerator ;
2017-03-06 01:28:59 +03:00
/** @var IConfig */
protected $config ;
2016-11-08 15:36:35 +03:00
2018-01-26 22:11:37 +03:00
/** @var \OC_Defaults */
private $defaults ;
2017-03-10 22:00:34 +03:00
/** @var string */
protected $serverRoot ;
2017-03-21 23:53:20 +03:00
/** @var ICache */
protected $depsCache ;
2018-01-26 19:03:22 +03:00
/** @var null|string */
2018-01-26 22:11:37 +03:00
private $injectedVariables ;
2018-01-26 19:03:22 +03:00
2018-03-07 16:13:36 +03:00
/** @var ICacheFactory */
private $cacheFactory ;
2018-07-03 14:23:22 +03:00
/** @var IconsCacher */
private $iconsCacher ;
2018-07-24 18:30:39 +03:00
/** @var ICache */
private $isCachedCache ;
/** @var ITimeFactory */
private $timeFactory ;
2019-05-29 17:09:07 +03:00
/** @var IMemcache */
private $lockingCache ;
2020-10-15 16:59:21 +03:00
/** @var AppConfig */
private $appConfig ;
2019-05-29 17:09:07 +03:00
2016-11-08 15:36:35 +03:00
/**
2016-11-30 18:30:04 +03:00
* @ param ILogger $logger
2017-02-17 18:42:07 +03:00
* @ param Factory $appDataFactory
2016-11-30 18:30:04 +03:00
* @ param IURLGenerator $urlGenerator
2017-03-21 23:53:20 +03:00
* @ param IConfig $config
2017-02-17 18:42:07 +03:00
* @ param \OC_Defaults $defaults
2017-03-10 22:00:34 +03:00
* @ param string $serverRoot
2018-03-07 16:13:36 +03:00
* @ param ICacheFactory $cacheFactory
2018-07-04 19:50:49 +03:00
* @ param IconsCacher $iconsCacher
2018-07-24 18:30:39 +03:00
* @ param ITimeFactory $timeFactory
2016-11-08 15:36:35 +03:00
*/
2017-03-10 22:00:34 +03:00
public function __construct ( ILogger $logger ,
2017-02-17 18:42:07 +03:00
Factory $appDataFactory ,
2017-03-10 22:00:34 +03:00
IURLGenerator $urlGenerator ,
2017-03-06 01:28:59 +03:00
IConfig $config ,
2017-02-17 18:42:07 +03:00
\OC_Defaults $defaults ,
2017-03-21 23:53:20 +03:00
$serverRoot ,
2018-07-04 19:50:49 +03:00
ICacheFactory $cacheFactory ,
2018-07-24 18:30:39 +03:00
IconsCacher $iconsCacher ,
2020-10-15 16:59:21 +03:00
ITimeFactory $timeFactory ,
AppConfig $appConfig ) {
2020-10-05 16:12:57 +03:00
$this -> logger = $logger ;
$this -> appData = $appDataFactory -> get ( 'css' );
2016-11-10 18:16:33 +03:00
$this -> urlGenerator = $urlGenerator ;
2020-10-05 16:12:57 +03:00
$this -> config = $config ;
$this -> defaults = $defaults ;
$this -> serverRoot = $serverRoot ;
2018-03-07 16:13:36 +03:00
$this -> cacheFactory = $cacheFactory ;
2020-10-05 16:12:57 +03:00
$this -> depsCache = $cacheFactory -> createDistributed ( 'SCSS-deps-' . md5 ( $this -> urlGenerator -> getBaseUrl ()));
2019-10-15 12:56:02 +03:00
$this -> isCachedCache = $cacheFactory -> createDistributed ( 'SCSS-cached-' . md5 ( $this -> urlGenerator -> getBaseUrl ()));
2019-05-29 17:09:07 +03:00
$lockingCache = $cacheFactory -> createDistributed ( 'SCSS-locks-' . md5 ( $this -> urlGenerator -> getBaseUrl ()));
if ( ! ( $lockingCache instanceof IMemcache )) {
$lockingCache = new NullCache ();
}
$this -> lockingCache = $lockingCache ;
2018-07-04 19:50:49 +03:00
$this -> iconsCacher = $iconsCacher ;
2018-07-24 18:30:39 +03:00
$this -> timeFactory = $timeFactory ;
2020-10-15 16:59:21 +03:00
$this -> appConfig = $appConfig ;
2016-11-30 23:51:09 +03:00
}
2016-11-10 18:16:33 +03:00
2016-11-30 23:51:09 +03:00
/**
* Process the caching process if needed
2018-01-26 22:11:37 +03:00
*
2016-11-30 23:51:09 +03:00
* @ param string $root Root path to the nextcloud installation
* @ param string $file
2017-01-22 21:33:45 +03:00
* @ param string $app The app name
2016-11-30 23:51:09 +03:00
* @ return boolean
2018-01-26 22:11:37 +03:00
* @ throws NotPermittedException
2016-11-30 23:51:09 +03:00
*/
2018-01-26 22:11:37 +03:00
public function process ( string $root , string $file , string $app ) : bool {
2016-11-30 23:51:09 +03:00
$path = explode ( '/' , $root . '/' . $file );
2016-11-08 15:36:35 +03:00
2016-11-30 23:51:09 +03:00
$fileNameSCSS = array_pop ( $path );
2020-10-05 16:12:57 +03:00
$fileNameCSS = $this -> prependVersionPrefix ( $this -> prependBaseurlPrefix ( str_replace ( '.scss' , '.css' , $fileNameSCSS )), $app );
2016-11-09 15:39:08 +03:00
2020-10-05 16:12:57 +03:00
$path = implode ( '/' , $path );
2017-12-28 15:32:45 +03:00
$webDir = $this -> getWebDir ( $path , $app , $this -> serverRoot , \OC :: $WEBROOT );
2016-11-09 13:18:43 +03:00
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::process ordinary check follows' , [ 'app' => 'scss_cacher' ]);
2018-07-24 18:30:39 +03:00
if ( ! $this -> variablesChanged () && $this -> isCached ( $fileNameCSS , $app )) {
2018-07-03 14:23:22 +03:00
// Inject icons vars css if any
2019-05-29 17:09:07 +03:00
return $this -> injectCssVariablesIfAny ();
2016-11-08 15:36:35 +03:00
}
2018-07-03 14:23:22 +03:00
2018-07-24 18:30:39 +03:00
try {
$folder = $this -> appData -> getFolder ( $app );
} catch ( NotFoundException $e ) {
// creating css appdata folder
$folder = $this -> appData -> newFolder ( $app );
}
2019-05-29 17:09:07 +03:00
$lockKey = $webDir . '/' . $fileNameSCSS ;
if ( ! $this -> lockingCache -> add ( $lockKey , 'locked!' , 120 )) {
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::process could not get lock for ' . $lockKey . ' and will wait 10 seconds for cached file to be available' , [ 'app' => 'scss_cacher' ]);
2019-05-29 17:09:07 +03:00
$retry = 0 ;
sleep ( 1 );
while ( $retry < 10 ) {
2020-10-15 16:59:21 +03:00
$this -> appConfig -> clearCachedConfig ();
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::process check in while loop follows' , [ 'app' => 'scss_cacher' ]);
2019-05-29 17:09:07 +03:00
if ( ! $this -> variablesChanged () && $this -> isCached ( $fileNameCSS , $app )) {
// Inject icons vars css if any
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( " SCSSCacher::process cached file for app ' $app ' and file ' $fileNameCSS ' is now available after $retry s. Moving on... " , [ 'app' => 'scss_cacher' ]);
2019-05-29 17:09:07 +03:00
return $this -> injectCssVariablesIfAny ();
}
2020-06-19 15:39:31 +03:00
sleep ( 1 );
2019-05-29 17:09:07 +03:00
$retry ++ ;
}
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::process Giving up scss caching for ' . $lockKey , [ 'app' => 'scss_cacher' ]);
2019-05-29 17:09:07 +03:00
return false ;
}
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::process Lock acquired for ' . $lockKey , [ 'app' => 'scss_cacher' ]);
2019-05-29 17:09:07 +03:00
try {
$cached = $this -> cache ( $path , $fileNameCSS , $fileNameSCSS , $folder , $webDir );
} catch ( \Exception $e ) {
$this -> lockingCache -> remove ( $lockKey );
throw $e ;
}
// Cleaning lock
$this -> lockingCache -> remove ( $lockKey );
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::process Lock removed for ' . $lockKey , [ 'app' => 'scss_cacher' ]);
2018-07-03 14:23:22 +03:00
// Inject icons vars css if any
if ( $this -> iconsCacher -> getCachedCSS () && $this -> iconsCacher -> getCachedCSS () -> getSize () > 0 ) {
$this -> iconsCacher -> injectCss ();
}
return $cached ;
2016-11-08 15:36:35 +03:00
}
2017-02-17 18:42:07 +03:00
/**
* @ param $appName
* @ param $fileName
* @ return ISimpleFile
*/
2018-01-26 22:11:37 +03:00
public function getCachedCSS ( string $appName , string $fileName ) : ISimpleFile {
2020-10-05 16:12:57 +03:00
$folder = $this -> appData -> getFolder ( $appName );
2018-03-27 11:21:54 +03:00
$cachedFileName = $this -> prependVersionPrefix ( $this -> prependBaseurlPrefix ( $fileName ), $appName );
2018-07-03 14:23:22 +03:00
2018-03-27 11:21:54 +03:00
return $folder -> getFile ( $cachedFileName );
2017-02-17 18:42:07 +03:00
}
2016-11-09 15:39:08 +03:00
/**
* Check if the file is cached or not
2016-11-30 23:51:09 +03:00
* @ param string $fileNameCSS
2018-07-24 18:30:39 +03:00
* @ param string $app
2016-11-09 15:39:08 +03:00
* @ return boolean
*/
2018-07-24 18:30:39 +03:00
private function isCached ( string $fileNameCSS , string $app ) {
$key = $this -> config -> getSystemValue ( 'version' ) . '/' . $app . '/' . $fileNameCSS ;
2019-05-29 17:09:07 +03:00
// If the file mtime is more recent than our cached one,
// let's consider the file is properly cached
if ( $cacheValue = $this -> isCachedCache -> get ( $key )) {
2018-07-24 18:30:39 +03:00
if ( $cacheValue > $this -> timeFactory -> getTime ()) {
return true ;
}
}
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( " SCSSCacher::isCached $fileNameCSS isCachedCache is expired or unset " , [ 'app' => 'scss_cacher' ]);
2018-07-24 18:30:39 +03:00
2019-05-29 17:09:07 +03:00
// Creating file cache if none for further checks
2018-07-24 18:30:39 +03:00
try {
$folder = $this -> appData -> getFolder ( $app );
} catch ( NotFoundException $e ) {
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( " SCSSCacher::isCached app data folder for $app could not be fetched " , [ 'app' => 'scss_cacher' ]);
2019-05-29 17:09:07 +03:00
return false ;
2018-07-24 18:30:39 +03:00
}
2019-05-29 17:09:07 +03:00
// Checking if file size is coherent
// and if one of the css dependency changed
2017-02-17 13:45:08 +03:00
try {
2016-11-30 23:51:09 +03:00
$cachedFile = $folder -> getFile ( $fileNameCSS );
2017-03-09 22:29:50 +03:00
if ( $cachedFile -> getSize () > 0 ) {
2017-03-21 23:53:20 +03:00
$depFileName = $fileNameCSS . '.deps' ;
2020-10-05 16:12:57 +03:00
$deps = $this -> depsCache -> get ( $folder -> getName () . '-' . $depFileName );
2017-03-21 23:53:20 +03:00
if ( $deps === null ) {
$depFile = $folder -> getFile ( $depFileName );
2020-10-05 16:12:57 +03:00
$deps = $depFile -> getContent ();
2019-05-29 17:09:07 +03:00
// Set to memcache for next run
2017-03-21 23:53:20 +03:00
$this -> depsCache -> set ( $folder -> getName () . '-' . $depFileName , $deps );
}
$deps = json_decode ( $deps , true );
2017-03-09 22:29:50 +03:00
2018-07-03 14:23:22 +03:00
foreach (( array ) $deps as $file => $mtime ) {
2017-03-09 22:29:50 +03:00
if ( ! file_exists ( $file ) || filemtime ( $file ) > $mtime ) {
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( " SCSSCacher::isCached $fileNameCSS is not considered as cached due to deps file $file " , [ 'app' => 'scss_cacher' ]);
2017-03-09 22:29:50 +03:00
return false ;
}
}
2018-07-03 14:23:22 +03:00
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( " SCSSCacher::isCached $fileNameCSS dependencies successfully cached for 5 minutes " , [ 'app' => 'scss_cacher' ]);
// It would probably make sense to adjust this timeout to something higher and see if that has some effect then
2018-07-24 18:30:39 +03:00
$this -> isCachedCache -> set ( $key , $this -> timeFactory -> getTime () + 5 * 60 );
2018-01-26 19:02:54 +03:00
return true ;
2016-11-09 13:18:43 +03:00
}
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( " SCSSCacher::isCached $fileNameCSS is not considered as cached cacheValue: $cacheValue " , [ 'app' => 'scss_cacher' ]);
2018-01-26 19:02:54 +03:00
return false ;
2018-07-03 14:23:22 +03:00
} catch ( NotFoundException $e ) {
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( " SCSSCacher::isCached NotFoundException " . $e -> getMessage (), [ 'app' => 'scss_cacher' ]);
2016-11-09 13:18:43 +03:00
return false ;
}
2017-02-17 13:45:08 +03:00
}
2017-02-17 18:42:07 +03:00
/**
* Check if the variables file has changed
* @ return bool
*/
2018-01-26 22:11:37 +03:00
private function variablesChanged () : bool {
2020-11-06 13:26:53 +03:00
$cachedVariables = $this -> config -> getAppValue ( 'core' , 'theming.variables' , '' );
$injectedVariables = $this -> getInjectedVariables ( $cachedVariables );
if ( $cachedVariables !== md5 ( $injectedVariables )) {
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::variablesChanged storedVariables: ' . json_encode ( $this -> config -> getAppValue ( 'core' , 'theming.variables' )) . ' currentInjectedVariables: ' . json_encode ( $injectedVariables ), [ 'app' => 'scss_cacher' ]);
2019-05-29 17:09:07 +03:00
$this -> config -> setAppValue ( 'core' , 'theming.variables' , md5 ( $injectedVariables ));
2019-10-15 12:56:27 +03:00
$this -> resetCache ();
2017-02-17 18:42:07 +03:00
return true ;
}
return false ;
}
2016-11-09 15:39:08 +03:00
/**
* Cache the file with AppData
2018-01-26 22:11:37 +03:00
*
2016-11-30 23:51:09 +03:00
* @ param string $path
* @ param string $fileNameCSS
* @ param string $fileNameSCSS
* @ param ISimpleFolder $folder
* @ param string $webDir
2016-11-09 15:39:08 +03:00
* @ return boolean
2018-01-26 22:11:37 +03:00
* @ throws NotPermittedException
2016-11-09 15:39:08 +03:00
*/
2018-01-26 22:11:37 +03:00
private function cache ( string $path , string $fileNameCSS , string $fileNameSCSS , ISimpleFolder $folder , string $webDir ) {
2016-11-08 15:36:35 +03:00
$scss = new Compiler ();
2017-02-17 13:45:08 +03:00
$scss -> setImportPaths ([
$path ,
2018-07-03 14:23:22 +03:00
$this -> serverRoot . '/core/css/'
2017-02-17 13:45:08 +03:00
]);
2018-07-03 14:23:22 +03:00
2017-09-26 16:29:37 +03:00
// Continue after throw
$scss -> setIgnoreErrors ( true );
2018-07-03 14:23:22 +03:00
if ( $this -> config -> getSystemValue ( 'debug' )) {
2016-11-08 15:36:35 +03:00
// Debug mode
2016-11-30 18:30:04 +03:00
$scss -> setFormatter ( Expanded :: class );
2016-11-08 15:36:35 +03:00
$scss -> setLineNumberStyle ( Compiler :: LINE_COMMENTS );
} else {
2016-11-09 13:18:43 +03:00
// Compression
2016-11-30 18:30:04 +03:00
$scss -> setFormatter ( Crunched :: class );
2016-11-08 15:36:35 +03:00
}
try {
2016-11-30 23:51:09 +03:00
$cachedfile = $folder -> getFile ( $fileNameCSS );
2018-07-03 14:23:22 +03:00
} catch ( NotFoundException $e ) {
2016-11-30 23:51:09 +03:00
$cachedfile = $folder -> newFile ( $fileNameCSS );
2016-11-09 13:18:43 +03:00
}
2017-03-09 22:29:50 +03:00
$depFileName = $fileNameCSS . '.deps' ;
try {
$depFile = $folder -> getFile ( $depFileName );
} catch ( NotFoundException $e ) {
$depFile = $folder -> newFile ( $depFileName );
}
2016-11-09 15:39:08 +03:00
// Compile
2016-11-09 13:18:43 +03:00
try {
2017-02-17 13:45:08 +03:00
$compiledScss = $scss -> compile (
2018-07-10 18:52:31 +03:00
'$webroot: \'' . $this -> getRoutePrefix () . '\';' .
2018-08-29 17:46:12 +03:00
$this -> getInjectedVariables () .
2017-02-17 13:45:08 +03:00
'@import "variables.scss";' .
2018-07-03 14:23:22 +03:00
'@import "functions.scss";' .
'@import "' . $fileNameSCSS . '";' );
} catch ( ParserException $e ) {
2020-10-15 18:13:35 +03:00
$this -> logger -> logException ( $e , [ 'app' => 'scss_cacher' ]);
2018-07-03 14:23:22 +03:00
2016-11-08 15:36:35 +03:00
return false ;
}
2016-11-08 23:45:43 +03:00
2018-07-03 14:23:22 +03:00
// Parse Icons and create related css variables
$compiledScss = $this -> iconsCacher -> setIconsCss ( $compiledScss );
2017-03-26 17:30:08 +03:00
// Gzip file
2016-11-09 15:39:08 +03:00
try {
2017-03-29 09:11:51 +03:00
$gzipFile = $folder -> getFile ( $fileNameCSS . '.gzip' ); # Safari doesn't like .gz
2017-03-26 17:30:08 +03:00
} catch ( NotFoundException $e ) {
2017-03-29 09:11:51 +03:00
$gzipFile = $folder -> newFile ( $fileNameCSS . '.gzip' ); # Safari doesn't like .gz
2017-03-26 17:30:08 +03:00
}
try {
$data = $this -> rebaseUrls ( $compiledScss , $webDir );
$cachedfile -> putContent ( $data );
2017-05-21 23:10:00 +03:00
$deps = json_encode ( $scss -> getParsedFiles ());
$depFile -> putContent ( $deps );
$this -> depsCache -> set ( $folder -> getName () . '-' . $depFileName , $deps );
2017-03-29 00:13:59 +03:00
$gzipFile -> putContent ( gzencode ( $data , 9 ));
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::cache ' . $webDir . '/' . $fileNameSCSS . ' compiled and successfully cached' , [ 'app' => 'scss_cacher' ]);
2018-07-03 14:23:22 +03:00
2016-11-08 23:45:43 +03:00
return true ;
2018-07-03 14:23:22 +03:00
} catch ( NotPermittedException $e ) {
2020-10-15 18:13:35 +03:00
$this -> logger -> error ( 'SCSSCacher::cache unable to cache: ' . $fileNameSCSS , [ 'app' => 'scss_cacher' ]);
2018-07-03 14:23:22 +03:00
2016-11-09 15:39:08 +03:00
return false ;
2016-11-08 23:45:43 +03:00
}
2016-11-08 15:36:35 +03:00
}
2017-02-17 18:42:07 +03:00
/**
* Reset scss cache by deleting all generated css files
* We need to regenerate all files when variables change
*/
2018-01-26 19:46:42 +03:00
public function resetCache () {
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::resetCache' , [ 'app' => 'scss_cacher' ]);
2019-10-15 12:56:27 +03:00
if ( ! $this -> lockingCache -> add ( 'resetCache' , 'locked!' , 120 )) {
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::resetCache Locked' , [ 'app' => 'scss_cacher' ]);
2019-10-15 12:56:27 +03:00
return ;
}
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::resetCache Lock acquired' , [ 'app' => 'scss_cacher' ]);
2018-01-26 21:07:30 +03:00
$this -> injectedVariables = null ;
2019-05-29 17:09:07 +03:00
// do not clear locks
$this -> cacheFactory -> createDistributed ( 'SCSS-deps-' ) -> clear ();
$this -> cacheFactory -> createDistributed ( 'SCSS-cached-' ) -> clear ();
2017-02-17 18:42:07 +03:00
$appDirectory = $this -> appData -> getDirectoryListing ();
foreach ( $appDirectory as $folder ) {
foreach ( $folder -> getDirectoryListing () as $file ) {
2018-04-30 18:50:56 +03:00
try {
$file -> delete ();
2018-07-03 14:23:22 +03:00
} catch ( NotPermittedException $e ) {
2020-10-15 18:13:35 +03:00
$this -> logger -> logException ( $e , [ 'message' => 'SCSSCacher::resetCache unable to delete file: ' . $file -> getName (), 'app' => 'scss_cacher' ]);
2018-04-30 18:50:56 +03:00
}
2017-02-17 18:42:07 +03:00
}
}
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::resetCache css cache cleared!' , [ 'app' => 'scss_cacher' ]);
2019-10-15 12:56:27 +03:00
$this -> lockingCache -> remove ( 'resetCache' );
2020-10-15 18:13:35 +03:00
$this -> logger -> debug ( 'SCSSCacher::resetCache Locking removed' , [ 'app' => 'scss_cacher' ]);
2017-02-17 18:42:07 +03:00
}
/**
* @ return string SCSS code for variables from OC_Defaults
*/
2020-11-06 13:26:53 +03:00
private function getInjectedVariables ( string $cache = '' ) : string {
2018-01-26 22:11:37 +03:00
if ( $this -> injectedVariables !== null ) {
2018-01-26 19:03:22 +03:00
return $this -> injectedVariables ;
2018-01-26 22:11:37 +03:00
}
2017-02-17 18:42:07 +03:00
$variables = '' ;
foreach ( $this -> defaults -> getScssVariables () as $key => $value ) {
2018-08-29 17:46:12 +03:00
$variables .= '$' . $key . ': ' . $value . ' !default;' ;
2017-02-17 18:42:07 +03:00
}
2018-01-26 19:03:22 +03:00
2020-11-06 13:26:53 +03:00
/*
* If we are trying to return the same variables as that are cached
* Then there is no need to do the compile step
*/
if ( $cache === md5 ( $variables )) {
$this -> injectedVariables = $variables ;
return $variables ;
}
2018-01-26 19:03:22 +03:00
// check for valid variables / otherwise fall back to defaults
try {
$scss = new Compiler ();
$scss -> compile ( $variables );
$this -> injectedVariables = $variables ;
} catch ( ParserException $e ) {
2020-10-15 18:13:35 +03:00
$this -> logger -> logException ( $e , [ 'app' => 'scss_cacher' ]);
2018-01-26 19:03:22 +03:00
}
2017-02-17 18:42:07 +03:00
return $variables ;
}
2016-11-08 15:36:35 +03:00
2016-11-09 15:39:08 +03:00
/**
* Add the correct uri prefix to make uri valid again
* @ param string $css
2016-11-30 23:51:09 +03:00
* @ param string $webDir
2016-11-09 15:39:08 +03:00
* @ return string
*/
2018-01-26 22:11:37 +03:00
private function rebaseUrls ( string $css , string $webDir ) : string {
2020-10-05 16:12:57 +03:00
$re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x' ;
2018-07-03 14:23:22 +03:00
$subst = 'url(\'' . $webDir . '/$1\')' ;
2016-11-08 15:36:35 +03:00
return preg_replace ( $re , $subst , $css );
}
2016-11-09 15:39:08 +03:00
/**
* Return the cached css file uri
2016-11-10 18:36:58 +03:00
* @ param string $appName the app name
2016-11-30 23:51:09 +03:00
* @ param string $fileName
2016-11-09 15:39:08 +03:00
* @ return string
*/
2018-01-26 22:11:37 +03:00
public function getCachedSCSS ( string $appName , string $fileName ) : string {
2016-12-22 17:41:13 +03:00
$tmpfileLoc = explode ( '/' , $fileName );
2020-10-05 16:12:57 +03:00
$fileName = array_pop ( $tmpfileLoc );
$fileName = $this -> prependVersionPrefix ( $this -> prependBaseurlPrefix ( str_replace ( '.scss' , '.css' , $fileName )), $appName );
2016-11-30 23:51:09 +03:00
2018-08-01 13:42:49 +03:00
return substr ( $this -> urlGenerator -> linkToRoute ( 'core.Css.getCss' , [
'fileName' => $fileName ,
'appName' => $appName ,
2019-05-29 17:09:07 +03:00
'v' => $this -> config -> getAppValue ( 'core' , 'theming.variables' , '0' )
2018-08-01 13:42:49 +03:00
]), \strlen ( \OC :: $WEBROOT ) + 1 );
2016-11-08 15:36:35 +03:00
}
2017-06-15 13:35:16 +03:00
2017-06-15 23:14:39 +03:00
/**
* Prepend hashed base url to the css file
2018-03-27 11:21:54 +03:00
* @ param string $cssFile
2017-06-15 23:14:39 +03:00
* @ return string
*/
2018-01-26 22:11:37 +03:00
private function prependBaseurlPrefix ( string $cssFile ) : string {
2018-07-10 18:52:31 +03:00
return substr ( md5 ( $this -> urlGenerator -> getBaseUrl () . $this -> getRoutePrefix ()), 0 , 4 ) . '-' . $cssFile ;
}
2018-07-03 14:23:22 +03:00
2018-07-10 18:52:31 +03:00
private function getRoutePrefix () {
$frontControllerActive = ( $this -> config -> getSystemValue ( 'htaccess.IgnoreFrontController' , false ) === true || getenv ( 'front_controller_active' ) === 'true' );
$prefix = \OC :: $WEBROOT . '/index.php' ;
if ( $frontControllerActive ) {
$prefix = \OC :: $WEBROOT ;
}
return $prefix ;
2018-03-27 11:21:54 +03:00
}
/**
* Prepend hashed app version hash
* @ param string $cssFile
* @ param string $appId
* @ return string
*/
private function prependVersionPrefix ( string $cssFile , string $appId ) : string {
$appVersion = \OC_App :: getAppVersion ( $appId );
if ( $appVersion !== '0' ) {
return substr ( md5 ( $appVersion ), 0 , 4 ) . '-' . $cssFile ;
}
$coreVersion = \OC_Util :: getVersionString ();
2018-07-03 14:23:22 +03:00
2018-03-27 11:21:54 +03:00
return substr ( md5 ( $coreVersion ), 0 , 4 ) . '-' . $cssFile ;
2017-06-15 13:35:16 +03:00
}
2017-12-27 18:44:14 +03:00
/**
2017-12-28 12:25:10 +03:00
* Get WebDir root
2017-12-27 18:44:14 +03:00
* @ param string $path the css file path
2017-12-28 15:32:45 +03:00
* @ param string $appName the app name
* @ param string $serverRoot the server root path
* @ param string $webRoot the nextcloud installation root path
2017-12-27 18:44:14 +03:00
* @ return string the webDir
*/
2018-01-26 22:11:37 +03:00
private function getWebDir ( string $path , string $appName , string $serverRoot , string $webRoot ) : string {
2017-12-28 12:28:16 +03:00
// Detect if path is within server root AND if path is within an app path
2018-07-03 14:23:22 +03:00
if ( strpos ( $path , $serverRoot ) === false && $appWebPath = \OC_App :: getAppWebPath ( $appName )) {
2017-12-28 12:25:10 +03:00
// Get the file path within the app directory
2017-12-28 15:32:45 +03:00
$appDirectoryPath = explode ( $appName , $path )[ 1 ];
2017-12-28 12:25:10 +03:00
// Remove the webroot
2018-07-03 14:23:22 +03:00
return str_replace ( $webRoot , '' , $appWebPath . $appDirectoryPath );
2017-12-27 18:44:14 +03:00
}
2018-07-03 14:23:22 +03:00
return $webRoot . substr ( $path , strlen ( $serverRoot ));
2017-12-27 18:44:14 +03:00
}
2019-05-29 17:09:07 +03:00
/**
2020-01-23 16:52:16 +03:00
* Add the icons css cache in the header if needed
2019-05-29 17:09:07 +03:00
*
* @ return boolean true
*/
private function injectCssVariablesIfAny () {
// Inject icons vars css if any
if ( $this -> iconsCacher -> getCachedCSS () && $this -> iconsCacher -> getCachedCSS () -> getSize () > 0 ) {
$this -> iconsCacher -> injectCss ();
}
return true ;
}
2016-11-08 15:36:35 +03:00
}