first commit

This commit is contained in:
salam committed 2026-10-06 19:40:28 +02:00
commit e81864e683
2984 files changed
+267797

No files matched your search

@@ -0,0 +1,133 @@
<?php declare(strict_types=1);
/*
* This file is part of the package bk2k/bootstrap-package.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace BK2K\BootstrapPackage\Parser;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* AbstractParser
*/
abstract class AbstractParser implements ParserInterface
{
/**
* @param string $extension
* @return bool
*/
public function supports(string $extension): bool
{
return false;
}
/**
* @param string $file
* @param array $settings
* @return string
*/
public function compile(string $file, array $settings): string
{
return $file;
}
/**
* @param string $file
* @param array $settings
* @return bool
*/
protected function isCached(string $file, array $settings): bool
{
$cacheIdentifier = $this->getCacheIdentifier($file, $settings);
$cacheFile = $this->getCacheFile($cacheIdentifier, $settings['cache']['tempDirectory']);
$cacheFileMeta = $this->getCacheFileMeta($cacheFile);
return file_exists($cacheFile) && file_exists($cacheFileMeta);
}
/**
* @param string $cacheFile
* @param string $cacheFileMeta
* @param array $settings
* @return bool
*/
protected function needsCompile(string $cacheFile, string $cacheFileMeta, array $settings): bool
{
$needCompilation = false;
$fileModificationTime = filemtime($cacheFile);
$metadata = unserialize((string) file_get_contents($cacheFileMeta), ['allowed_classes' => false]);
foreach ($metadata['files'] as $file) {
if (filemtime($file) > $fileModificationTime) {
$needCompilation = true;
break;
}
}
if (!$needCompilation && $settings['variables'] !== $metadata['variables']) {
$needCompilation = true;
}
if (!$needCompilation && $settings['options']['sourceMap'] !== $metadata['sourceMap']) {
$needCompilation = true;
}
return $needCompilation;
}
/**
* @param string $cacheIdentifier
* @param string $tempDirectory
* @return string
*/
protected function getCacheFile(string $cacheIdentifier, string $tempDirectory): string
{
return $tempDirectory . $cacheIdentifier . '.css';
}
/**
* @param string $filename
* @return string
*/
protected function getCacheFileMeta(string $filename)
{
return $filename . '.meta';
}
/**
* @param string $file
* @param array $settings
* @return string
*/
protected function getCacheIdentifier(string $file, array $settings): string
{
$filehash = md5($file);
$hash = hash('sha256', $filehash . serialize($settings));
$extension = pathinfo($file, PATHINFO_EXTENSION);
return basename($file, '.' . $extension) . '-' . $hash;
}
/**
* @return string
*/
protected function getPathSite(): string
{
return Environment::getPublicPath() . '/';
}
/**
* Clear all page caches
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheGroupException
*/
protected function clearPageCaches(): void
{
GeneralUtility::makeInstance(CacheManager::class)->flushCachesInGroup('pages');
}
}
@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
/*
* This file is part of the package bk2k/bootstrap-package.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace BK2K\BootstrapPackage\Parser;
/**
* ParserInterface
*/
interface ParserInterface
{
/**
* @param string $extension
* @return bool
*/
public function supports(string $extension): bool;
/**
* @param string $file
* @param array $settings
* @return string
*/
public function compile(string $file, array $settings): string;
}
@@ -0,0 +1,171 @@
<?php declare(strict_types=1);
/*
* This file is part of the package bk2k/bootstrap-package.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace BK2K\BootstrapPackage\Parser;
use ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\OutputStyle;
use ScssPhp\ScssPhp\Version;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* ScssParser
*/
class ScssParser extends AbstractParser
{
/**
* Constructor
*/
public function __construct()
{
if (!class_exists('ScssPhp\ScssPhp\Version')) {
require_once ExtensionManagementUtility::extPath('bootstrap_package') . '/Contrib/scssphp/scss.inc.php';
}
}
/**
* @param string $extension
* @return bool
*/
public function supports(string $extension): bool
{
return $extension === 'scss';
}
/**
* @param string $file
* @param array $settings
* @return string
*/
public function compile(string $file, array $settings): string
{
$cacheIdentifier = $this->getCacheIdentifier($file, $settings);
$cacheFile = $this->getCacheFile($cacheIdentifier, $settings['cache']['tempDirectory']);
$cacheFileMeta = $this->getCacheFileMeta($cacheFile);
$compile = false;
if (!$this->isCached($file, $settings)
|| $this->needsCompile($cacheFile, $cacheFileMeta, $settings)) {
$compile = true;
}
if ($compile) {
$result = $this->parseFile($file, $settings);
GeneralUtility::writeFile(GeneralUtility::getFileAbsFileName($cacheFile), $result['css']);
GeneralUtility::writeFile(GeneralUtility::getFileAbsFileName($cacheFileMeta), serialize($result['cache']));
$this->clearPageCaches();
}
return $cacheFile;
}
/**
* @param string $file
* @param array $settings
* @return array
*/
protected function parseFile(string $file, array $settings): array
{
$scss = new Compiler();
$scss->setOutputStyle(OutputStyle::COMPRESSED);
$scss->addVariables($settings['variables']);
if ($settings['options']['sourceMap']) {
$scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
$scss->setSourceMapOptions([
'sourceMapRootpath' => $settings['cache']['tempDirectoryRelativeToRoot'],
'sourceMapBasepath' => '<PATH DOES NOT EXIST BUT SUPRESSES WARNINGS>'
]);
}
$absoluteFilename = $settings['file']['absolute'];
// Adds visual directory path of the initial file as import path
// This scenarios happens, when e.g. developing packages using the `path`
// repository feature of Composer - having one package in `public/typo3conf/ext/`
// and the other one symlinked in e.g. `packages/`.
// Since the PHP SCSS parser works on resolved real paths, the symlinked context is lost.
$visualImportPath = dirname($absoluteFilename);
$scss->addImportPath(function ($url) use ($visualImportPath): ?string {
// Resolve potential back paths manually using PathUtility::getCanonicalPath,
// but make sure we do not break out of TYPO3 application path using GeneralUtility::getFileAbsFileName
// Also resolve EXT: paths if given
$isTypo3Absolute = (strpos($url, 'EXT:') === 0) || PathUtility::isAbsolutePath($url);
$fileName = $isTypo3Absolute ? $url : $visualImportPath . '/' . $url;
$full = GeneralUtility::getFileAbsFileName(PathUtility::getCanonicalPath($fileName));
// The API forces us to check the existence of files paths, with or without url.
// We must only return a string if the file to be imported actually exists.
$hasExtension = (bool) preg_match('/[.]s?css$/', $url);
if (
is_file($file = $full . '.scss') ||
($hasExtension && is_file($file = $full))
) {
// We could trigger a deprecation message here at some point
return $file;
}
return null;
});
// Add extensions path to import paths, so that we can use paths relative to this directory to resolve imports
$scss->addImportPath(Environment::getExtensionsPath());
// Make paths in url() statements relative to site root
$absoluteFilePath = dirname($absoluteFilename);
$relativeFilePath = PathUtility::getAbsoluteWebPath($absoluteFilePath);
$absoluteBootstrapPackageThemePath =
ExtensionManagementUtility::extPath('bootstrap_package') . 'Resources/Public/Scss/Theme';
$relativeBootstrapPackageThemePath = PathUtility::getAbsoluteWebPath($absoluteBootstrapPackageThemePath);
$scss->registerFunction(
'url',
function ($args) use (
$scss,
$absoluteFilePath,
$relativeFilePath,
$absoluteBootstrapPackageThemePath,
$relativeBootstrapPackageThemePath
) : string {
$marker = $args[0][1];
$args[0][1] = '';
$result = $scss->compileValue($args[0]);
if (substr_compare($result, 'data:', 0, 5, true) !== 0) {
if (is_file(PathUtility::getCanonicalPath($absoluteFilePath . '/' . $result))) {
$result = PathUtility::getCanonicalPath($relativeFilePath . '/' . $result);
} elseif (is_file(PathUtility::getCanonicalPath($absoluteBootstrapPackageThemePath . '/' . $result))) {
$result = PathUtility::getCanonicalPath($relativeBootstrapPackageThemePath . '/' . $result);
}
$result = substr($result, 0, 1) === '/' ? substr($result, 1) : $result;
}
return 'url(' . $marker . $result . $marker . ')';
}
);
// Compile file
$compilationResult = $scss->compileString('@import "' . $absoluteFilename . '"');
$css = $compilationResult->getCss();
// Fix paths in url() statements to be relative to temp directory
$relativeTempPath = $settings['cache']['tempDirectoryRelativeToRoot'];
$search = '%url\s*\(\s*[\\\'"]?(?!(((?:https?:)?\/\/)|(?:data:?:)))([^\\\'")]+)[\\\'"]?\s*\)%';
$replace = 'url("' . $relativeTempPath . '$3")';
$css = (string) preg_replace($search, $replace, $css);
return [
'css' => $css,
'cache' => [
'version' => Version::VERSION,
'date' => date('r'),
'css' => $css,
'etag' => md5($css),
'files' => $compilationResult->getIncludedFiles(),
'variables' => $settings['variables'],
'sourceMap' => $settings['options']['sourceMap']
]
];
}
}