Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for builder specific templates #9654

Merged
merged 8 commits into from Feb 13, 2021
1 change: 1 addition & 0 deletions app/bundles/CoreBundle/Config/config.php
Expand Up @@ -842,6 +842,7 @@
'mautic.helper.core_parameters',
'mautic.filesystem',
'symfony.finder',
'mautic.integrations.helper.builder_integrations',
],
'methodCalls' => [
'setDefaultTheme' => [
Expand Down
7 changes: 7 additions & 0 deletions app/bundles/CoreBundle/Controller/ThemeController.php
Expand Up @@ -13,6 +13,8 @@

use Mautic\CoreBundle\Form\Type\ThemeUploadType;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\ThemeHelper;
use Mautic\IntegrationsBundle\Helper\BuilderIntegrationsHelper;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
Expand All @@ -39,7 +41,11 @@ public function indexAction()
return $this->accessDenied();
}

/** @var ThemeHelper $themeHelper */
$themeHelper = $this->container->get('mautic.helper.theme');
/** @var BuilderIntegrationsHelper $builderIntegrationsHelper */
$builderIntegrationsHelper = $this->container->get('mautic.integrations.helper.builder_integrations');

$dir = $this->factory->getSystemPath('themes', true);
$action = $this->generateUrl('mautic_themes_index');
$form = $this->get('form.factory')->create(ThemeUploadType::class, [], ['action' => $action]);
Expand Down Expand Up @@ -96,6 +102,7 @@ public function indexAction()
return $this->delegateView([
'viewParameters' => [
'items' => $themeHelper->getInstalledThemes('all', true, true),
'builders' => $builderIntegrationsHelper->getBuilderNames(),
'defaultThemes' => $themeHelper->getDefaultThemes(),
'form' => $form->createView(),
'permissions' => $permissions,
Expand Down
165 changes: 110 additions & 55 deletions app/bundles/CoreBundle/Helper/ThemeHelper.php
Expand Up @@ -14,8 +14,9 @@
use Mautic\CoreBundle\Exception\BadConfigurationException;
use Mautic\CoreBundle\Exception\FileExistsException;
use Mautic\CoreBundle\Exception\FileNotFoundException;
use Mautic\CoreBundle\Helper\Filesystem;
use Mautic\CoreBundle\Templating\Helper\ThemeHelper as TemplatingThemeHelper;
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
use Mautic\IntegrationsBundle\Helper\BuilderIntegrationsHelper;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateReference;
Expand Down Expand Up @@ -63,6 +64,31 @@ class ThemeHelper
*/
private $themeHelpers = [];

/**
* @var CoreParametersHelper
*/
private $coreParametersHelper;

/**
* @var BuilderIntegrationsHelper
*/
private $builderIntegrationsHelper;

/**
* @var Filesystem
*/
private $filesystem;

/**
* @var Finder
*/
private $finder;

/**
* @var bool
*/
private $themesLoadedFromFilesystem = false;

/**
* Default themes which cannot be deleted.
*
Expand All @@ -86,35 +112,22 @@ class ThemeHelper
'vibrant',
];

/**
* @var CoreParametersHelper
*/
private $coreParametersHelper;

/**
* @var Filesystem
*/
private $filesystem;

/**
* @var Finder
*/
private $finder;

public function __construct(
PathsHelper $pathsHelper,
TemplatingHelper $templatingHelper,
TranslatorInterface $translator,
CoreParametersHelper $coreParametersHelper,
Filesystem $filesystem,
Finder $finder
Finder $finder,
BuilderIntegrationsHelper $builderIntegrationsHelper
) {
$this->pathsHelper = $pathsHelper;
$this->templatingHelper = $templatingHelper;
$this->translator = $translator;
$this->coreParametersHelper = $coreParametersHelper;
$this->filesystem = clone $filesystem;
$this->finder = clone $finder;
$this->pathsHelper = $pathsHelper;
$this->templatingHelper = $templatingHelper;
$this->translator = $translator;
$this->coreParametersHelper = $coreParametersHelper;
$this->builderIntegrationsHelper = $builderIntegrationsHelper;
$this->filesystem = clone $filesystem;
$this->finder = clone $finder;
}

/**
Expand Down Expand Up @@ -328,42 +341,17 @@ public function checkForTwigTemplate($template)
*/
public function getInstalledThemes($specificFeature = 'all', $extended = false, $ignoreCache = false, $includeDirs = true)
{
if (empty($this->themes[$specificFeature]) || $ignoreCache) {
$dir = $this->pathsHelper->getSystemPath('themes', true);
$this->finder->directories()->depth('0')->ignoreDotFiles(true)->in($dir);

$this->themes[$specificFeature] = [];
$this->themesInfo[$specificFeature] = [];
foreach ($this->finder as $theme) {
if (!$this->filesystem->exists($theme->getRealPath().'/config.json')) {
continue;
}

$config = json_decode($this->filesystem->readFile($theme->getRealPath().'/config.json'), true);

if ('all' === $specificFeature || (isset($config['features']) && in_array($specificFeature, $config['features']))) {
$this->themes[$specificFeature][$theme->getBasename()] = $config['name'];
$this->themesInfo[$specificFeature][$theme->getBasename()] = [];
$this->themesInfo[$specificFeature][$theme->getBasename()]['name'] = $config['name'];
$this->themesInfo[$specificFeature][$theme->getBasename()]['key'] = $theme->getBasename();
$this->themesInfo[$specificFeature][$theme->getBasename()]['config'] = $config;

if ($includeDirs) {
$this->themesInfo[$specificFeature][$theme->getBasename()]['dir'] = $theme->getRealPath();
$this->themesInfo[$specificFeature][$theme->getBasename()]['themesLocalDir'] = $this->pathsHelper->getSystemPath(
'themes',
false
);
}
}
}
// Use a concatenated key since $includeDirs changes what's returned ($includeDirs used by API controller to prevent from exposing file paths)
$key = $specificFeature.(int) $includeDirs;
if (empty($this->themes[$key]) || $ignoreCache) {
$this->loadThemes($specificFeature, $includeDirs, $key);
}

if ($extended) {
return $this->themesInfo[$specificFeature];
return $this->themesInfo[$key];
}

return $this->themes[$specificFeature];
return $this->themes[$key];
}

/**
Expand Down Expand Up @@ -544,7 +532,7 @@ public function getExtractError($archive)
*
* @return string
*
* @throws Exception
* @throws \Exception
*/
public function zip($themeName)
{
Expand Down Expand Up @@ -610,4 +598,71 @@ private function findThemeWithTemplate(EngineInterface $templating, TemplateRefe
}
}
}

private function loadThemes(string $specificFeature, bool $includeDirs, string $key): void
{
if (!$this->themesLoadedFromFilesystem) {
$this->themesLoadedFromFilesystem = true;
// prevent the finder from duplicating directories in its internal state
// https://symfony.com/doc/current/components/finder.html#usage
$dir = $this->pathsHelper->getSystemPath('themes', true);
$this->finder->directories()->depth('0')->ignoreDotFiles(true)->in($dir);
}

$this->themes[$key] = [];
$this->themesInfo[$key] = [];

foreach ($this->finder as $theme) {
if (!$this->filesystem->exists($theme->getRealPath().'/config.json')) {
continue;
}

$config = json_decode($this->filesystem->readFile($theme->getRealPath().'/config.json'), true);

if (!$this->shouldLoadTheme($config, $specificFeature)) {
continue;
}

$this->themes[$key][$theme->getBasename()] = $config['name'];

$this->themesInfo[$key][$theme->getBasename()] = [];
$this->themesInfo[$key][$theme->getBasename()]['name'] = $config['name'];
$this->themesInfo[$key][$theme->getBasename()]['key'] = $theme->getBasename();
$this->themesInfo[$key][$theme->getBasename()]['config'] = $config;

if (!$includeDirs) {
continue;
}

$this->themesInfo[$key][$theme->getBasename()]['dir'] = $theme->getRealPath();
$this->themesInfo[$key][$theme->getBasename()]['themesLocalDir'] = $this->pathsHelper->getSystemPath('themes');
}
}

private function shouldLoadTheme(array $config, string $featureRequested): bool
{
if ('all' === $featureRequested) {
return true;
}

if (!isset($config['features'])) {
return false;
}

if (!in_array($featureRequested, $config['features'])) {
return false;
}

try {
$builder = $this->builderIntegrationsHelper->getBuilder($featureRequested);
$builderName = $builder->getName();
} catch (IntegrationNotFoundException $exception) {
// Assume legacy builder
$builderName = 'legacy';
}

$builderRequested = $config['builder'] ?? 'legacy';

return $builderName === $builderRequested;
}
}