Skip to content

Commit

Permalink
Adding an option to hide themes (#13723)
Browse files Browse the repository at this point in the history
* Merge pull request #2032 from acquia/MAUT-10202

MAUT-10202 : Hide/Unhide themes

* CI fixes

* This migration doesn't make sense for the community

* Test fixes

* Update icons to use Remix

* Moving the `disabled-row` css class from autogenerated file and adding it to the less file and running `grunt compile-less` to generated it to css properly

* Fixing "Key "visibility" for array with keys "name, key, config, dir, themesLocalDir" does not exist."

---------

Co-authored-by: Tejas Navghane <ts.navghane@gmail.com>
  • Loading branch information
escopecz and ts-navghane committed May 13, 2024
1 parent 7f141f6 commit ca2bf45
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 51 deletions.
5 changes: 5 additions & 0 deletions app/bundles/CoreBundle/Assets/css/app.css
Expand Up @@ -5781,3 +5781,8 @@ ul.media-list.media-list-feed div.media-object {
.flip-vertically {
transform: scaleY(-1);
}
.disabled-row {
opacity: 0.6;
pointer-events: none;
background-color: #f2f2f2;
}
8 changes: 7 additions & 1 deletion app/bundles/CoreBundle/Assets/css/app/less/custom.less
Expand Up @@ -365,4 +365,10 @@ ul.media-list.media-list-feed div.media-object

.flip-vertically {
transform: scaleY(-1);
}
}

.disabled-row {
opacity: 0.6;
pointer-events: none;
background-color: #f2f2f2;
}
4 changes: 3 additions & 1 deletion app/bundles/CoreBundle/Assets/js/9.modals.js
Expand Up @@ -299,6 +299,8 @@ Mautic.showConfirmation = function (el) {
var confirmCallback = mQuery(el).data('confirm-callback');
var cancelText = mQuery(el).data('cancel-text');
var cancelCallback = mQuery(el).data('cancel-callback');
const confirmBtnClass = mQuery(el).data('confirm-btn-class')
? mQuery(el).data('confirm-btn-class') : 'btn btn-danger';

var confirmContainer = mQuery("<div />").attr({"class": "modal fade confirmation-modal"});
var confirmDialogDiv = mQuery("<div />").attr({"class": "modal-dialog"});
Expand All @@ -307,7 +309,7 @@ Mautic.showConfirmation = function (el) {
var confirmHeaderDiv = mQuery("<div />").attr({"class": "modal-header"});
confirmHeaderDiv.append(mQuery('<h4 />').attr({"class": "modal-title"}).text(message));
var confirmButton = mQuery('<button type="button" />')
.addClass("btn btn-danger")
.addClass(confirmBtnClass)
.css("marginRight", "5px")
.css("marginLeft", "5px")
.click(function () {
Expand Down
84 changes: 84 additions & 0 deletions app/bundles/CoreBundle/Controller/ThemeController.php
Expand Up @@ -2,10 +2,14 @@

namespace Mautic\CoreBundle\Controller;

use Mautic\CoreBundle\Exception\BadConfigurationException;
use Mautic\CoreBundle\Exception\FileNotFoundException;
use Mautic\CoreBundle\Form\Type\ThemeUploadType;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\ThemeHelperInterface;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\IntegrationsBundle\Helper\BuilderIntegrationsHelper;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -273,4 +277,84 @@ public function getIndexPostActionVars(): array
],
];
}

/**
* Change default theme's visibility.
*/
public function visibilityAction(string $objectId, Request $request, CorePermissions $corePermissions, ThemeHelperInterface $themeHelper): Response
{
if (!$corePermissions->isGranted('core:themes:view')) {
return $this->accessDenied();
}

$flashes = [];

if (Request::METHOD_POST === $request->getMethod()) {
$flashes = $this->visibility($objectId, $themeHelper);
}

return $this->postActionRedirect(
array_merge($this->getIndexPostActionVars(), [
'flashes' => $flashes,
])
);
}

/**
* @return array<mixed>
*/
private function visibility(string $themeName, ThemeHelperInterface $themeHelper): array
{
if (!$themeHelper->exists($themeName)) {
return [
[
'type' => 'error',
'msg' => 'mautic.core.theme.error.notfound',
'msgVars' => ['%theme%' => $themeName],
],
];
}

if (!in_array($themeName, $themeHelper->getDefaultThemes())) {
return [
[
'type' => 'error',
'msg' => 'mautic.core.theme.cannot.change.visibility',
'msgVars' => ['%theme%' => $themeName],
],
];
}

$flashes = [];

try {
$theme = $themeHelper->getTheme($themeName);
$themeHelper->toggleVisibility($themeName);
$flashes[] = [
'type' => 'notice',
'msg' => 'mautic.core.theme.visibility.changed',
'msgVars' => ['%theme%' => $theme->getName()],
];
} catch (IOException) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.core.theme.visibility.error',
'msgVars' => ['%error%' => 'Failed to change the theme visibility'],
];
} catch (BadConfigurationException) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.core.theme.visibility.error',
'msgVars' => ['%error%' => sprintf('Theme %s not configured properly: builder property in the config.json', $themeName)],
];
} catch (FileNotFoundException) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.core.theme.visibility.error',
'msgVars' => ['%error%' => sprintf('Theme %s not found', $themeName)],
];
}

return $flashes;
}
}
137 changes: 136 additions & 1 deletion app/bundles/CoreBundle/Helper/ThemeHelper.php
Expand Up @@ -8,12 +8,16 @@
use Mautic\CoreBundle\Twig\Helper\ThemeHelper as twigThemeHelper;
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
use Mautic\IntegrationsBundle\Helper\BuilderIntegrationsHelper;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;

class ThemeHelper implements ThemeHelperInterface
{
public const HIDDEN_THEMES_TXT = 'hidden-themes.txt';

/**
* @var array<string, mixed[]>
*/
Expand Down Expand Up @@ -72,6 +76,11 @@ class ThemeHelper implements ThemeHelperInterface
'vibrant',
];

/**
* @var array<int, string>
*/
private array $hiddenThemes = [];

public function __construct(
private PathsHelper $pathsHelper,
private Environment $twig,
Expand All @@ -90,6 +99,14 @@ public function getDefaultThemes()
return $this->defaultThemes;
}

/**
* @param string[] $themes
*/
public function addDefaultThemes(array $themes): void
{
$this->defaultThemes = array_merge($this->defaultThemes, $themes);
}

public function setDefaultTheme($defaultTheme): void
{
$this->defaultTheme = $defaultTheme;
Expand Down Expand Up @@ -174,6 +191,12 @@ public function delete($theme): void
throw new FileNotFoundException($theme.' not found!');
}

if (in_array($theme, $this->getDefaultThemes(), true)) {
$this->addToHidden($theme);

return;
}

$this->filesystem->remove($root.$theme);
}

Expand Down Expand Up @@ -469,7 +492,13 @@ private function loadThemes(string $specificFeature, bool $includeDirs, string $
if (empty($config['builder']) || !is_array($config['builder'])) {
$config['builder'] = ['legacy'];
}
$this->themesInfo[$key][$theme->getBasename()]['config'] = $config;

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

if (empty($this->themesInfo[$key][$theme->getBasename()]['visibility'])) {
unset($this->themesInfo[$key][$theme->getBasename()]['visibility']);
}

if (!$includeDirs) {
continue;
Expand All @@ -478,6 +507,8 @@ private function loadThemes(string $specificFeature, bool $includeDirs, string $
$this->themesInfo[$key][$theme->getBasename()]['dir'] = $theme->getRealPath();
$this->themesInfo[$key][$theme->getBasename()]['themesLocalDir'] = $this->pathsHelper->getSystemPath('themes');
}

$this->sortThemesInfo($key);
}

private function shouldLoadTheme(array $config, string $featureRequested): bool
Expand Down Expand Up @@ -520,4 +551,108 @@ public function getCurrentTheme(string $template, string $specificFeature): stri

return $template;
}

/**
* @return array|string[]
*/
private function getHiddenThemes(): array
{
if (count($this->hiddenThemes)) {
return $this->hiddenThemes;
}

if (!$this->filesystem->exists($hidden = $this->pathsHelper->getThemesPath().'/'.self::HIDDEN_THEMES_TXT)) {
return [];
}

return $this->hiddenThemes = array_map(fn ($item) => trim($item), explode('|', $this->filesystem->readFile($hidden)));
}

/**
* @throws IOException
*/
private function addToHidden(string $theme): void
{
$hidden = $this->createHiddenTxtIfNotExists();
$this->filesystem->appendToFile($hidden, sprintf('|%s', $theme));
}

/**
* @return array<string, bool>
*/
private function getVisibility(SplFileInfo $theme): array
{
$themeName = $theme->getBasename();

if (!in_array($themeName, $this->defaultThemes, true)) {
return [];
}

return ['hidden' => in_array($themeName, $this->getHiddenThemes(), true)];
}

/**
* @throws IOException
*/
public function toggleVisibility(string $themeName): void
{
if (!in_array($themeName, $this->getDefaultThemes(), true)) {
return;
}

$hidden = $this->createHiddenTxtIfNotExists();
$hiddenThemes = array_values(array_filter(array_unique(explode('|', $this->filesystem->readFile($hidden)))));

if (in_array($themeName, $hiddenThemes, true)) {
$this->removeFromHidden($themeName, $hiddenThemes);
} else {
$this->addToHidden($themeName);
}
}

private function sortThemesInfo(string $key): void
{
$hiddenThemes = [];
$themes = [];

foreach ($this->themesInfo[$key] as $data) {
if (isset($data['visibility']['hidden']) && $data['visibility']['hidden']) {
$hiddenThemes[$key][$data['key']] = $data;
} else {
$themes[$key][$data['key']] = $data;
}
}

$this->themesInfo[$key] = array_merge($themes[$key] ?? [], $hiddenThemes[$key] ?? []);
}

private function createHiddenTxtIfNotExists(): string
{
if (!$this->filesystem->exists($hidden = $this->pathsHelper->getThemesPath().'/'.self::HIDDEN_THEMES_TXT)) {
$this->filesystem->touch($hidden);
}

return $hidden;
}

/**
* @param string[] $hiddenThemes
*
* @throws IOException
*/
private function removeFromHidden(string $themeName, array $hiddenThemes): void
{
$hidden = $this->createHiddenTxtIfNotExists();
$keyToRemove = array_search($themeName, $hiddenThemes, true);

if (false !== $keyToRemove) {
unset($hiddenThemes[$keyToRemove]);

if (empty($hiddenThemes)) {
$this->filesystem->remove($hidden);
} else {
$this->filesystem->dumpFile($hidden, sprintf('|%s', implode('|', $hiddenThemes)));
}
}
}
}
2 changes: 2 additions & 0 deletions app/bundles/CoreBundle/Helper/ThemeHelperInterface.php
Expand Up @@ -132,4 +132,6 @@ public function getExtractError(int $archive);
public function zip($themeName);

public function getCurrentTheme(string $template, string $specificFeature): string;

public function toggleVisibility(string $themeName): void;
}
Expand Up @@ -34,7 +34,7 @@
{% set hasThumbnail = is_file(themeInfo.dir ~ '/' ~ thumbnailName) %}
{% endif %}
{% set thumbnailUrl = getAssetUrl(themeInfo.themesLocalDir ~ '/' ~ themeKey ~ '/' ~ thumbnailName) %}
<div class="col-md-3 theme-list">
<div class="col-md-3 theme-list {{ themeInfo.visibility.hidden|default(false) is empty ? '' : 'hide' }}">
<div class="panel panel-default {% if isSelected %}theme-selected{% endif %}">
<div class="panel-body text-center">
<h3>{{ themeInfo.name }}</h3>
Expand Down

0 comments on commit ca2bf45

Please sign in to comment.