Commit 1f9ae779 authored by Tino Goratsch's avatar Tino Goratsch

Merge branch 'release/v4.29.0'

parents 36cd95d5 4f0fe1bd
Pipeline #29315423 passed with stages
in 10 minutes and 42 seconds
......@@ -16,7 +16,7 @@ interface BootstrapInterface extends HttpKernelInterface
/**
* Contains the current ACP3 version string.
*/
const VERSION = '4.28.4';
const VERSION = '4.29.0';
/**
* Performs some startup checks.
......
......@@ -34,7 +34,7 @@ class AddEsiSurrogateHeaderListener
/**
* @param ControllerActionAfterDispatchEvent $event
*/
public function execute(ControllerActionAfterDispatchEvent $event)
public function __invoke(ControllerActionAfterDispatchEvent $event)
{
$response = $event->getResponse();
......
......@@ -62,6 +62,7 @@ class AddTemplateVariablesListener
'DESIGN_PATH_ABSOLUTE' => $this->request->getScheme() . '://' . $this->request->getHttpHost() . $this->theme->getDesignPathWeb(),
'LANG_DIRECTION' => $this->translator->getDirection(),
'LANG' => $this->translator->getShortIsoCode(),
'UPLOADS_DIR' => $this->appPath->getWebRoot() . 'uploads/',
]);
}
}
......@@ -127,6 +127,9 @@ class Assets
/**
* @return string
*
* @throws \MJS\TopSort\CircularDependencyException
* @throws \MJS\TopSort\ElementNotFoundException
*/
public function getEnabledLibrariesAsString()
{
......
......@@ -9,6 +9,7 @@ namespace ACP3\Core\Assets;
use ACP3\Core\Assets\Event\AddLibraryEvent;
use ACP3\Core\Http\RequestInterface;
use MJS\TopSort\Implementations\StringSort;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class Libraries
......@@ -17,31 +18,16 @@ class Libraries
* @var array
*/
protected $libraries = [
'moment' => [
'enabled' => false,
'js' => 'moment.min.js',
],
'jquery' => [
'enabled' => true,
'enabled_for_ajax' => false,
'js' => 'jquery.min.js',
],
'js-cookie' => [
'enabled' => false,
'enabled_for_ajax' => false,
'js' => 'js.cookie.js',
],
'ajax-form' => [
'enabled' => true,
'enabled_for_ajax' => false,
'dependencies' => ['jquery'],
'dependencies' => ['bootstrap', 'jquery'],
'js' => 'ajax-form.js',
],
'fancybox' => [
'bootbox' => [
'enabled' => false,
'dependencies' => ['jquery'],
'css' => 'jquery.fancybox.css',
'js' => 'jquery.fancybox.min.js',
'dependencies' => ['bootstrap'],
'js' => 'bootbox.js',
],
'bootstrap' => [
'enabled' => false,
......@@ -55,26 +41,41 @@ class Libraries
'css' => 'dataTables.bootstrap.css',
'js' => 'jquery.dataTables.js',
],
'bootbox' => [
'enabled' => false,
'dependencies' => ['bootstrap'],
'js' => 'bootbox.js',
],
'datetimepicker' => [
'enabled' => false,
'dependencies' => ['jquery', 'moment'],
'css' => 'bootstrap-datetimepicker.css',
'js' => 'bootstrap-datetimepicker.min.js',
],
'fancybox' => [
'enabled' => false,
'dependencies' => ['jquery'],
'css' => 'jquery.fancybox.css',
'js' => 'jquery.fancybox.min.js',
],
'font-awesome' => [
'enabled' => false,
'css' => [
'fa-brands.css',
'fa-regular.css',
'fa-solid.css',
'brands.css',
'regular.css',
'solid.css',
'fontawesome.css',
],
],
'jquery' => [
'enabled' => true,
'enabled_for_ajax' => false,
'js' => 'jquery.min.js',
],
'js-cookie' => [
'enabled' => false,
'enabled_for_ajax' => false,
'js' => 'js.cookie.js',
],
'moment' => [
'enabled' => false,
'js' => 'moment.min.js',
],
];
/**
* @var EventDispatcherInterface
......@@ -106,10 +107,23 @@ class Libraries
/**
* @return array
*
* @throws \MJS\TopSort\CircularDependencyException
* @throws \MJS\TopSort\ElementNotFoundException
*/
public function getLibraries(): array
{
return $this->libraries;
$topSort = new StringSort();
foreach ($this->libraries as $library => $options) {
$topSort->add($library, $options['dependencies'] ?? []);
}
$librariesTopSorted = [];
foreach ($topSort->sort() as $library) {
$librariesTopSorted[$library] = $this->libraries[$library];
}
return $librariesTopSorted;
}
/**
......@@ -157,15 +171,18 @@ class Libraries
/**
* @return array
*
* @throws \MJS\TopSort\CircularDependencyException
* @throws \MJS\TopSort\ElementNotFoundException
*/
public function getEnabledLibraries(): array
{
$enabledLibraries = [];
foreach ($this->libraries as $library => $values) {
if ($this->includeInXmlHttpRequest($values)) {
foreach ($this->getLibraries() as $library => $options) {
if ($this->includeInXmlHttpRequest($options)) {
continue;
}
if ($values['enabled'] === false) {
if ($options['enabled'] === false) {
continue;
}
......
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Console;
use ACP3\Core\Application\BootstrapInterface;
use ACP3\Core\Console\DependencyInjection\ServiceContainerBuilder;
use ACP3\Core\Environment\ApplicationPath;
use ACP3\Core\Logger\LoggerFactory;
use Patchwork\Utf8\Bootup;
use Psr\Container\ContainerInterface;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
class Application
{
/**
* @var string
*/
private $environment;
/**
* @var ApplicationPath
*/
private $appPath;
/**
* @var ContainerInterface
*/
private $container;
/**
* Application constructor.
*
* @param string $environment
*
* @throws \Exception
*/
public function __construct(string $environment)
{
$this->environment = $environment;
$this->initializeApplicationPath();
$this->logger = (new LoggerFactory($this->appPath))->create('console');
}
private function initializeApplicationPath()
{
$this->appPath = new ApplicationPath($this->environment);
}
public function run(): int
{
$this->setErrorHandler();
$this->initializeClasses();
/** @var \Symfony\Component\Console\Application $console */
$console = $this->container->get('symfony_console');
$console->setName('ACP3 CMS console application');
$console->setVersion(BootstrapInterface::VERSION);
return $console->run();
}
/**
* Set monolog as the default PHP error handler.
*/
private function setErrorHandler()
{
ExceptionHandler::register(true);
$errorHandler = new ErrorHandler();
$errorHandler->setDefaultLogger($this->logger);
ErrorHandler::register($errorHandler);
}
public function initializeClasses()
{
Bootup::initAll(); // Enables the portability layer and configures PHP for UTF-8
Bootup::filterRequestUri(); // Redirects to an UTF-8 encoded URL if it's not already the case
Bootup::filterRequestInputs(); // Normalizes HTTP inputs to UTF-8 NFC
$this->container = ServiceContainerBuilder::create(
$this->logger,
$this->appPath,
$this->environment
);
}
}
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Console\Command;
use ACP3\Core\Cache\Purge;
use ACP3\Core\Environment\ApplicationPath;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class AssetsClearCommand extends Command
{
/**
* @var ApplicationPath
*/
private $applicationPath;
/**
* AssetsClearCommand constructor.
*
* @param \ACP3\Core\Environment\ApplicationPath $applicationPath
*/
public function __construct(ApplicationPath $applicationPath)
{
parent::__construct();
$this->applicationPath = $applicationPath;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('acp3:assets:clear')
->setDescription('Clears all locally stored assets.')
->setHelp('This command allows you to clear all the locally stored assets.');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->title('Clearing assets...');
Purge::doPurge($this->applicationPath->getUploadsDir() . 'assets');
$output->writeln('Done!');
}
}
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Console\Command;
use ACP3\Core\Cache\Purge;
use ACP3\Core\Environment\ApplicationPath;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class CacheClearCommand extends Command
{
/**
* @var ApplicationPath
*/
private $applicationPath;
/**
* ClearCacheCommand constructor.
*
* @param ApplicationPath $applicationPath
*/
public function __construct(ApplicationPath $applicationPath)
{
parent::__construct();
$this->applicationPath = $applicationPath;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('acp3:cache:clear')
->setDescription('Clears all locally stored caches.')
->setHelp('This command allows you to clear all the locally stored caches. This applies for the PhpFileCache only');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->title('Clearing paths...');
$paths = \glob(ACP3_ROOT_DIR . 'cache/*/*');
$length = \array_push($paths, $this->applicationPath->getUploadsDir() . 'assets');
$progress = new ProgressBar($output, $length);
ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message%');
$progress->setFormat('custom');
foreach ($paths as $path) {
$progress->setMessage($path);
Purge::doPurge($path);
$progress->advance();
}
$progress->finish();
}
}
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Console\Command;
use ACP3\Core\Installer\Model\SchemaUpdateModel;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ModulesUpdateCommand extends Command
{
/**
* @var SchemaUpdateModel
*/
private $schemaUpdateModel;
/**
* ModulesUpdateCommand constructor.
*
* @param \ACP3\Core\Installer\Model\SchemaUpdateModel $schemaUpdateModel
*/
public function __construct(SchemaUpdateModel $schemaUpdateModel)
{
parent::__construct();
$this->schemaUpdateModel = $schemaUpdateModel;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('acp3:modules:update')
->setDescription('Updates the database schema of all currently installed modules.');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->title('Updating installed modules...');
foreach ($this->schemaUpdateModel->updateModules() as $module => $result) {
$output->writeln($result === 1 ? "<info>{$module}</info>" : "<error>{$module}</error>");
}
}
}
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Console\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class RegisterCommandsCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$definition = $container->findDefinition('symfony_console');
$plugins = $container->findTaggedServiceIds('acp3.console.command');
foreach ($plugins as $serviceId => $tags) {
$definition->addMethodCall(
'add',
[new Reference($serviceId)]
);
}
}
}
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Console\DependencyInjection;
use ACP3\Core\Environment\ApplicationPath;
use ACP3\Core\Installer\DependencyInjection\RegisterInstallersCompilerPass;
use ACP3\Core\Modules\Modules;
use Psr\Log\LoggerInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
class ServiceContainerBuilder extends ContainerBuilder
{
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var ApplicationPath
*/
private $applicationPath;
/**
* @var string
*/
private $applicationMode;
/**
* ServiceContainerBuilder constructor.
*
* @param LoggerInterface $logger
* @param ApplicationPath $applicationPath
* @param string $applicationMode
*/
public function __construct(
LoggerInterface $logger,
ApplicationPath $applicationPath,
string $applicationMode
) {
parent::__construct();
$this->logger = $logger;
$this->applicationPath = $applicationPath;
$this->applicationMode = $applicationMode;
$this->setUpContainer();
}
private function setUpContainer()
{
$this->set('core.logger.system_logger', $this->logger);
$this->set('core.environment.application_path', $this->applicationPath);
$this->setParameter('core.environment', $this->applicationMode);
$this
->addCompilerPass(
new RegisterListenersPass(
'core.event_dispatcher',
'core.eventListener',
'core.eventSubscriber'
)
)
->addCompilerPass(new RegisterInstallersCompilerPass())
->addCompilerPass(new RegisterCommandsCompilerPass());
$loader = new YamlFileLoader($this, new FileLocator(__DIR__));
$loader->load($this->applicationPath->getClassesDir() . 'config/console.yml');
/** @var Modules $modules */
$modules = $this->get('core.modules');
foreach ($modules->getAllModulesTopSorted() as $module) {
$modulePath = $this->applicationPath->getModulesDir() . $module['vendor'] . '/' . $module['dir'];
$path = $modulePath . '/Resources/config/services.yml';
if (\is_file($path)) {
$loader->load($path);
}
}
$this->compile();
}
/**
* @param LoggerInterface $logger
* @param \ACP3\Core\Environment\ApplicationPath $applicationPath
* @param string $applicationMode
*
* @return ContainerBuilder
*/
public static function create(
LoggerInterface $logger,
ApplicationPath $applicationPath,
$applicationMode
) {
return new static($logger, $applicationPath, $applicationMode);
}
}
......@@ -9,8 +9,9 @@ namespace ACP3\Core\Environment;
class ApplicationMode
{
const PRODUCTION = 'prod';
const CLI = 'console';
const DEVELOPMENT = 'dev';
const INSTALLER = 'installer';