Commit 6cbbb54c authored by Tino Goratsch's avatar Tino Goratsch

Merge branch 'release/v4.27.0'

parents 598e35aa 4159b788
Pipeline #19843147 passed with stages
in 9 minutes and 56 seconds
......@@ -9,6 +9,7 @@ namespace ACP3\Core\Application;
use ACP3\Core\Environment\ApplicationPath;
use ACP3\Core\ErrorHandler;
use ACP3\Core\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
abstract class AbstractBootstrap implements BootstrapInterface
......@@ -32,12 +33,14 @@ abstract class AbstractBootstrap implements BootstrapInterface
/**
* @param string $appMode
*
* @throws \Exception
*/
public function __construct($appMode)
{
$this->appMode = $appMode;
$this->initializeApplicationPath();
$this->logger = (new \ACP3\Core\Logger\LoggerFactory($this->appPath))->create('system');
$this->logger = (new LoggerFactory($this->appPath))->create('error');
}
protected function initializeApplicationPath()
......
......@@ -57,7 +57,6 @@ class Bootstrap extends AbstractBootstrap
$this->container = new \ACP3ServiceContainer();
$this->container->set('core.environment.application_path', $this->appPath);
$this->container->set('core.http.symfony_request', $symfonyRequest);
$this->container->set('core.logger.system_logger', $this->logger);
}
/**
......@@ -70,10 +69,7 @@ class Bootstrap extends AbstractBootstrap
if (!$containerConfigCache->isFresh()) {
$containerBuilder = ServiceContainerBuilder::create(
$this->logger,
$this->appPath,
$symfonyRequest,
$this->appMode
$this->appPath, $symfonyRequest, $this->appMode
);
$dumper = new PhpDumper($containerBuilder);
......@@ -131,12 +127,7 @@ class Bootstrap extends AbstractBootstrap
$this->appPath
->setDesignPathWeb($this->appPath->getWebRoot() . $path)
->setDesignPathInternal($this->systemSettings['design'] . '/')
->setDesignPathAbsolute(
$this->container->get('core.http.request')->getScheme() . '://'
. $this->container->get('core.http.request')->getHttpHost()
. $this->appPath->getDesignPathWeb()
);
->setDesignPathInternal($this->systemSettings['design'] . '/');
}
/**
......
......@@ -16,7 +16,7 @@ interface BootstrapInterface extends HttpKernelInterface
/**
* Contains the current ACP3 version string.
*/
const VERSION = '4.26.4';
const VERSION = '4.27.0';
/**
* Performs some startup checks.
......
......@@ -11,10 +11,6 @@ use ACP3\Core;
class FileResolver
{
/**
* @var \ACP3\Core\XML
*/
protected $xml;
/**
* @var \ACP3\Core\Environment\ApplicationPath
*/
......@@ -23,10 +19,6 @@ class FileResolver
* @var \ACP3\Core\Assets\Cache
*/
protected $resourcesCache;
/**
* @var \ACP3\Core\Modules\Vendor
*/
protected $vendors;
/**
* @var array
*/
......@@ -39,24 +31,36 @@ class FileResolver
* @var string
*/
protected $designAssetsPath;
/**
* @var \ACP3\Core\Modules
*/
private $modules;
/**
* @var \ACP3\Core\Environment\Theme
*/
private $theme;
/**
* @var string
*/
private $currentTheme;
/**
* @param \ACP3\Core\XML $xml
* @param \ACP3\Core\Assets\Cache $resourcesCache
* @param \ACP3\Core\Environment\ApplicationPath $appPath
* @param \ACP3\Core\Modules\Vendor $vendors
* @param \ACP3\Core\Environment\Theme $theme
* @param \ACP3\Core\Modules $modules
*/
public function __construct(
Core\XML $xml,
Core\Assets\Cache $resourcesCache,
Core\Environment\ApplicationPath $appPath,
Core\Modules\Vendor $vendors
Core\Environment\Theme $theme,
Core\Modules $modules
) {
$this->xml = $xml;
$this->resourcesCache = $resourcesCache;
$this->appPath = $appPath;
$this->vendors = $vendors;
$this->cachedPaths = $resourcesCache->getCache();
$this->modules = $modules;
$this->theme = $theme;
}
/**
......@@ -77,8 +81,12 @@ class FileResolver
*
* @return string
*/
public function getStaticAssetPath(string $modulePath, string $designPath, string $dir = '', string $file = ''): string
{
public function getStaticAssetPath(
string $modulePath,
string $designPath,
string $dir = '',
string $file = ''
): string {
if ($this->needsTrailingSlash($modulePath)) {
$modulePath .= '/';
}
......@@ -120,7 +128,7 @@ class FileResolver
private function resolveAssetPath(string $modulePath, string $designPath, string $dir, string $file): string
{
if ($this->designAssetsPath === null) {
$this->designAssetsPath = $this->appPath->getDesignPathInternal();
$this->resetDesignAssetPath();
}
$assetPath = '';
......@@ -130,24 +138,26 @@ class FileResolver
if (\is_file($designAssetPath) === true) {
$assetPath = $designAssetPath;
} else {
$designInfo = $this->xml->parseXmlFile($this->designAssetsPath . '/info.xml', '/design');
$parentThemes = $this->theme->getThemeDependencies($this->currentTheme);
$parentTheme = \next($parentThemes);
// Recursively iterate over the nested themes
if (!empty($designInfo['parent'])) {
$this->designAssetsPath = $this->appPath->getDesignRootPathInternal() . $designInfo['parent'] . '/';
if ($parentTheme !== false) {
$this->modifyDesignAssetPath($parentTheme);
$assetPath = $this->getStaticAssetPath($modulePath, $designPath, $dir, $file);
$this->designAssetsPath = $this->appPath->getDesignPathInternal();
$this->resetDesignAssetPath();
return $assetPath;
}
// No overrides have been found -> iterate over all possible module namespaces
foreach (\array_reverse($this->vendors->getVendors()) as $vendor) {
$moduleAssetPath = $this->appPath->getModulesDir() . $vendor . '/' . $modulePath . $dir . $file;
$moduleName = \substr($modulePath, 0, \strpos($modulePath, '/'));
$moduleInfo = $this->modules->getModuleInfo($moduleName);
if (!empty($moduleInfo)) {
$moduleAssetPath = $this->appPath->getModulesDir() . $moduleInfo['vendor'] . '/' . $modulePath . $dir . $file;
if (\is_file($moduleAssetPath) === true) {
$assetPath = $moduleAssetPath;
break;
}
}
}
......@@ -166,7 +176,7 @@ class FileResolver
*/
public function resolveTemplatePath(string $template): string
{
// A path without any slash was given -> has to be the layout file of the current design
// A path without any slash was given -> has to be a layout file of the current design
if (\strpos($template, '/') === false) {
return $this->getStaticAssetPath('', '', '', $template);
}
......@@ -178,11 +188,19 @@ class FileResolver
}
$modulesPath = $fragments[0] . '/Resources/';
$designPath = $fragments[0];
$template = $fragments[1];
if (isset($fragments[2])) {
$template .= '/' . $fragments[2];
}
$template = \implode('/', \array_slice($fragments, 1));
return $this->getStaticAssetPath($modulesPath, $designPath, 'View', $template);
}
private function modifyDesignAssetPath(string $themeName): void
{
$this->currentTheme = $themeName;
$this->designAssetsPath = $this->appPath->getDesignRootPathInternal() . $themeName . '/';
}
private function resetDesignAssetPath(): void
{
$this->modifyDesignAssetPath($this->theme->getCurrentTheme());
}
}
......@@ -35,36 +35,30 @@ trait CacheResponseTrait
/**
* @param int $lifetime Cache TTL in seconds
*/
public function setCacheResponseCacheable($lifetime = 60)
public function setCacheResponseCacheable(int $lifetime = 60): void
{
$varyHeaderName = 'X-User-Context-Hash';
if ($this->canUsePageCache()) {
$varyHeaderName = 'X-User-Context-Hash';
$response = $this->getResponse();
$response
->setVary($varyHeaderName)
->setMaxAge($lifetime)
->setSharedMaxAge($lifetime)
->headers->add([
$varyHeaderName => $this->getRequest()->getSymfonyRequest()->headers->get($varyHeaderName),
]);
if ($this->disallowPageCache()) {
$response->setPrivate();
$lifetime = null;
} else {
$response->setPublic();
$this->getResponse()
->setPublic()
->setVary($varyHeaderName)
->setMaxAge($lifetime)
->setSharedMaxAge($lifetime)
->headers->add([
$varyHeaderName => $this->getRequest()->getSymfonyRequest()->headers->get($varyHeaderName),
]);
}
}
/**
* @return bool
*/
protected function disallowPageCache()
protected function canUsePageCache(): bool
{
$systemSettings = $this->getSettings()->getSettings(Schema::MODULE_NAME);
return $this->getApplicationMode() === ApplicationMode::DEVELOPMENT
|| $systemSettings['page_cache_is_enabled'] == 0;
return $this->getApplicationMode() !== ApplicationMode::DEVELOPMENT
&& $systemSettings['page_cache_is_enabled'] == 1;
}
}
......@@ -113,7 +113,7 @@ abstract class AbstractWidgetAction implements ActionInterface
'HOST_NAME' => $this->request->getHttpHost(),
'ROOT_DIR_ABSOLUTE' => $this->request->getScheme() . '://' . $this->request->getHttpHost() . $this->appPath->getWebRoot(),
'DESIGN_PATH' => $this->appPath->getDesignPathWeb(),
'DESIGN_PATH_ABSOLUTE' => $this->appPath->getDesignPathAbsolute(),
'DESIGN_PATH_ABSOLUTE' => $this->request->getScheme() . '://' . $this->request->getHttpHost() . $this->appPath->getDesignPathWeb(),
'LANG_DIRECTION' => $this->translator->getDirection(),
'LANG' => $this->translator->getShortIsoCode(),
]);
......@@ -134,7 +134,10 @@ abstract class AbstractWidgetAction implements ActionInterface
*/
protected function applyTemplateAutomatically()
{
return $this->request->getModule() . '/' . \ucfirst($this->request->getArea()) . '/' . $this->request->getController() . '.' . $this->request->getAction() . '.tpl';
return $this->request->getModule()
. '/' . \ucfirst($this->request->getArea())
. '/' . $this->request->getController()
. '.' . $this->request->getAction() . '.tpl';
}
/**
......
......@@ -16,7 +16,6 @@ use ACP3\Core\Modules;
use ACP3\Core\Validation\DependencyInjection\RegisterValidationRulesPass;
use ACP3\Core\View\Renderer\Smarty\DependencyInjection\RegisterSmartyPluginsPass;
use ACP3\Core\WYSIWYG\DependencyInjection\RegisterWysiwygEditorsCompilerPass;
use Psr\Log\LoggerInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
......@@ -25,10 +24,6 @@ use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class ServiceContainerBuilder extends ContainerBuilder
{
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var ApplicationPath
*/
......@@ -45,20 +40,15 @@ class ServiceContainerBuilder extends ContainerBuilder
/**
* ServiceContainerBuilder constructor.
*
* @param LoggerInterface $logger
* @param ApplicationPath $applicationPath
* @param SymfonyRequest $symfonyRequest
* @param string $applicationMode
*/
public function __construct(
LoggerInterface $logger,
ApplicationPath $applicationPath,
SymfonyRequest $symfonyRequest,
$applicationMode
ApplicationPath $applicationPath, SymfonyRequest $symfonyRequest, $applicationMode
) {
parent::__construct();
$this->logger = $logger;
$this->applicationPath = $applicationPath;
$this->symfonyRequest = $symfonyRequest;
$this->applicationMode = $applicationMode;
......@@ -68,7 +58,6 @@ class ServiceContainerBuilder extends ContainerBuilder
private function setUpContainer()
{
$this->set('core.logger.system_logger', $this->logger);
$this->set('core.http.symfony_request', $this->symfonyRequest);
$this->set('core.environment.application_path', $this->applicationPath);
$this->setParameter('core.environment', $this->applicationMode);
......@@ -112,7 +101,6 @@ class ServiceContainerBuilder extends ContainerBuilder
}
/**
* @param LoggerInterface $logger
* @param \ACP3\Core\Environment\ApplicationPath $applicationPath
* @param SymfonyRequest $symfonyRequest
* @param string $applicationMode
......@@ -120,12 +108,9 @@ class ServiceContainerBuilder extends ContainerBuilder
* @return ContainerBuilder
*/
public static function create(
LoggerInterface $logger,
ApplicationPath $applicationPath,
SymfonyRequest $symfonyRequest,
$applicationMode
ApplicationPath $applicationPath, SymfonyRequest $symfonyRequest, $applicationMode
) {
return new static($logger, $applicationPath, $symfonyRequest, $applicationMode);
return new static($applicationPath, $symfonyRequest, $applicationMode);
}
/**
......
......@@ -59,7 +59,7 @@ class ApplicationPath
*
* @param string $applicationMode
*/
public function __construct($applicationMode)
public function __construct(string $applicationMode)
{
$this->phpSelf = \htmlentities($_SERVER['SCRIPT_NAME']);
$this->webRoot = \substr($this->phpSelf, 0, \strrpos($this->phpSelf, '/') + 1);
......@@ -74,147 +74,63 @@ class ApplicationPath
/**
* @return string
*/
public function getPhpSelf()
public function getPhpSelf(): string
{
return $this->phpSelf;
}
/**
* @param string $phpSelf
*
* @return ApplicationPath
*/
public function setPhpSelf($phpSelf)
{
$this->phpSelf = $phpSelf;
return $this;
}
/**
* @return string
*/
public function getWebRoot()
public function getWebRoot(): string
{
return $this->webRoot;
}
/**
* @param string $webRoot
*
* @return ApplicationPath
*/
public function setWebRoot($webRoot)
{
$this->webRoot = $webRoot;
return $this;
}
/**
* @return string
*/
public function getAppDir()
public function getAppDir(): string
{
return $this->appDir;
}
/**
* @param string $appDir
*
* @return ApplicationPath
*/
public function setAppDir($appDir)
{
$this->appDir = $appDir;
return $this;
}
/**
* @return string
*/
public function getClassesDir()
public function getClassesDir(): string
{
return $this->classesDir;
}
/**
* @param string $classesDir
*
* @return ApplicationPath
*/
public function setClassesDir($classesDir)
{
$this->classesDir = $classesDir;
return $this;
}
/**
* @return string
*/
public function getModulesDir()
public function getModulesDir(): string
{
return $this->modulesDir;
}
/**
* @param string $modulesDir
*
* @return ApplicationPath
*/
public function setModulesDir($modulesDir)
{
$this->modulesDir = $modulesDir;
return $this;
}
/**
* @return string
*/
public function getUploadsDir()
public function getUploadsDir(): string
{
return $this->uploadsDir;
}
/**
* @param string $uploadsDir
*
* @return ApplicationPath
*/
public function setUploadsDir($uploadsDir)
{
$this->uploadsDir = $uploadsDir;
return $this;
}
/**
* @return string
*/
public function getCacheDir()
public function getCacheDir(): string
{
return $this->cacheDir;
}
/**
* @param string $cacheDir
*
* @return ApplicationPath
*/
public function setCacheDir($cacheDir)
{
$this->cacheDir = $cacheDir;
return $this;
}
/**
* @return string
*/
public function getDesignRootPathInternal()
public function getDesignRootPathInternal(): string
{
return $this->designRootPathInternal;
}
......@@ -234,7 +150,7 @@ class ApplicationPath
/**
* @return string
*/
public function getDesignPathInternal()
public function getDesignPathInternal(): string
{
return $this->designPathInternal;
}
......@@ -254,7 +170,7 @@ class ApplicationPath
/**
* @return string
*/
public function getDesignPathWeb()
public function getDesignPathWeb(): string
{
return $this->designPathWeb;
}
......@@ -273,8 +189,10 @@ class ApplicationPath
/**
* @return string
*
* @deprecated Will be removed with version 5.0.0
*/
public function getDesignPathAbsolute()
public function getDesignPathAbsolute(): string
{
return $this->designPathAbsolute;
}
......@@ -283,6 +201,8 @@ class ApplicationPath
* @param string $designPathAbsolute
*
* @return ApplicationPath
*
* @deprecated Will be removed with version 5.0.0
*/
public function setDesignPathAbsolute($designPathAbsolute)
{
......
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Environment;
use ACP3\Core\Settings\SettingsInterface;
use ACP3\Core\XML;
use ACP3\Modules\ACP3\System\Installer\Schema;
class Theme
{
/**
* @var \ACP3\Core\Settings\SettingsInterface
*/
private $settings;
/**
* @var \ACP3\Core\XML
*/
private $xml;
/**
* @var array
*/
private $availableThemes = [];
/**
* @var array
*/
private $sortedThemeDependencies = [];
/**
* Design constructor.
*
* @param \ACP3\Core\Settings\SettingsInterface $settings
* @param \ACP3\Core\XML $xml
*/
public function __construct(
SettingsInterface $settings,
XML $xml
) {
$this->settings = $settings;
$this->xml = $xml;
}
public function getAvailableThemes(): array
{
if (empty($this->availableThemes)) {
$this->setAvailableThemes();
}
return $this->availableThemes;
}
private function setAvailableThemes(): void
{
$designs = \glob(ACP3_ROOT_DIR . '/designs/*/info.xml');
if ($designs === false) {
return;
}
foreach ($designs as $design) {
$designInfo = $this->xml->parseXmlFile($design, '/design');
if (!empty($designInfo)) {
$identifier = $this->getThemeInternalName($design);
$this->availableThemes[$identifier] = $designInfo;
}
}
}
/**
* @param string $file
*
* @return string
*/
private function getThemeInternalName(string $file): string
{
$path = \dirname($file);
$pathParts = \explode('/', $path);
return $pathParts[\count($pathParts) - 1];
}
public function getCurrentTheme(): string
{
return $this->settings->getSettings(Schema::MODULE_NAME)['design'];
}
/**
* @return array
*/
public function getCurrentThemeDependencies(): array
{
return $this->getThemeDependencies($this->getCurrentTheme());
}
/**
* @param string $themeName
*
* @return array
*/
public function getThemeDependencies(string $themeName): array
{
if (!isset($this->sortedThemeDependencies[$themeName])) {
$this->setThemeDependencies($themeName);
}
return $this->sortedThemeDependencies[$themeName];
}
private function setThemeDependencies(string $themeName): void
{
$availableThemes = $this->getAvailableThemes();
if (!isset($availableThemes[$themeName]['parent'])) {