Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Block/Adminhtml/Config/Form/Field/FixVirtualThemes.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function render(AbstractElement $element)
return <<<HTML
<tr>
<td colspan="100">
<div class="button-container">
<div class="fix-themes-container">
<button id="fix-all-themes"
class="button action-configure"
type="button"
Expand Down
57 changes: 50 additions & 7 deletions Block/Adminhtml/Config/Form/Field/VirtualCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,68 @@

namespace Swissup\Core\Block\Adminhtml\Config\Form\Field;

use Magento\Framework\App\ObjectManager;
use Magento\Backend\Block\Template\Context;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Magento\Theme\Model\ResourceModel\Theme\Collection;
use Magento\Framework\View\Design\ThemeInterface;
use Magento\Theme\Model\ResourceModel\Theme\CollectionFactory;
use Swissup\Core\Model\Theme\SourceFiles;

class VirtualCheck extends \Magento\Config\Block\System\Config\Form\Field
{
protected $_template = 'config/field/virtual_check.phtml';


private CollectionFactory $collectionFactory;

private SourceFiles $sourceFiles;

private ?array $themes = null;

public function __construct(
Context $context,
CollectionFactory $collectionFactory,
SourceFiles $sourceFiles,
array $data = []
) {
parent::__construct($context, $data);

$this->collectionFactory = $collectionFactory;
$this->sourceFiles = $sourceFiles;
}

public function render(AbstractElement $element)
{
$this->assign('configElement', $element);
$html = $this->toHtml();

return $this->_decorateRowHtml($element, "<td class='themes-table' colspan=\"3\">$html</td>");
}

public function getVirtualThemes()
public function getVirtualThemes(): array
{
return ObjectManager::getInstance()->create(Collection::class)
->addFieldToFilter('type', 1);
if ($this->themes === null) {
$this->themes = [];

$collection = $this->collectionFactory->create()
->addFieldToFilter('type', ThemeInterface::TYPE_VIRTUAL);

foreach ($collection as $theme) {
$this->themes[] = $this->collectThemeData($theme);
}
}

return $this->themes;
}

private function collectThemeData(ThemeInterface $theme): array
{
$unreadable = $this->sourceFiles->getUnreadable($theme);

return [
'title' => $theme->getThemeTitle(),
'path' => $theme->getFullPath(),
'status' => $unreadable
? __('Virtual (Magento can\'t read %1)', implode(', ', $unreadable))
: __('Virtual'),
];
}
}
51 changes: 46 additions & 5 deletions Controller/Adminhtml/Theme/FixVirtualThemes.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,71 @@

use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\View\Design\ThemeInterface;
use Magento\Theme\Model\ResourceModel\Theme\CollectionFactory;
use Swissup\Core\Model\Theme\SourceFiles;

class FixVirtualThemes extends \Magento\Backend\App\Action
{
private CollectionFactory $collectionFactory;

private SourceFiles $sourceFiles;

public function __construct(
Context $context,
CollectionFactory $collectionFactory
CollectionFactory $collectionFactory,
SourceFiles $sourceFiles
) {
parent::__construct($context);
$this->collectionFactory = $collectionFactory;
$this->sourceFiles = $sourceFiles;
}

public function execute()
{
$virtualThemes = $this->collectionFactory->create()->addFieldToFilter('type', 1);
{
$virtualThemes = $this->collectionFactory->create()
->addFieldToFilter('type', ThemeInterface::TYPE_VIRTUAL);

$fixed = 0;
$skipped = [];
foreach ($virtualThemes as $theme) {
$theme->setType(0)->save();
// Physical theme without source files breaks the storefront with
// "Required parameter 'theme_dir' was not passed"
if (!$this->sourceFiles->makeReadable($theme)) {
$skipped[] = $theme->getThemeTitle();
continue;
}

$theme->setType(ThemeInterface::TYPE_PHYSICAL)->save();
$fixed++;
}

$resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON);
$resultJson->setData(['message' => 'Virtual themes fixed. Please, clear the cache!']);
$resultJson->setData([
'fixed' => $fixed,
'skipped' => count($skipped),
'message' => $this->getMessage($fixed, $skipped),
]);

return $resultJson;
}

private function getMessage(int $fixed, array $skipped): string
{
if (!$skipped) {
return (string) __('Virtual themes fixed.');
}

$skippedList = implode(', ', $skipped);

if (!$fixed) {
return (string) __('Nothing was fixed. Magento is unable to read the source files of: %1', $skippedList);
}

return (string) __(
'%1 theme(s) fixed. Magento is unable to read the source files of: %2',
$fixed,
$skippedList
);
}
}
96 changes: 96 additions & 0 deletions Model/Theme/SourceFiles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace Swissup\Core\Model\Theme;

use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Component\ComponentRegistrarInterface;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Filesystem\Driver\File;
use Magento\Framework\View\Design\ThemeInterface;

class SourceFiles
{
const THEME_FILES = ['registration.php', 'theme.xml'];

const READABLE_FILE_MODE = 0644;

private ComponentRegistrarInterface $registrar;

private File $filesystemDriver;

public function __construct(
ComponentRegistrarInterface $registrar,
File $filesystemDriver
) {
$this->registrar = $registrar;
$this->filesystemDriver = $filesystemDriver;
}

public function getUnreadable(ThemeInterface $theme): array
{
$directory = $this->getDirectory($theme);

$unreadable = [];
foreach (self::THEME_FILES as $filename) {
if (!$directory || !$this->isReadable($directory . '/' . $filename)) {
$unreadable[] = $filename;
}
}

return $unreadable;
}

public function makeReadable(ThemeInterface $theme): bool
{
$directory = $this->getDirectory($theme);
if (!$directory) {
return false;
}

foreach (self::THEME_FILES as $filename) {
$path = $directory . '/' . $filename;
if ($this->isReadable($path)) {
continue;
}

if (!$this->changePermissions($path) || !$this->isReadable($path)) {
return false;
}
}

return true;
}

/**
* Themes created in admin have no theme_path. Theme with unreadable
* directory is not registered either - it is silently skipped by the glob
* in app/etc/NonComposerComponentRegistration.php, so an unregistered theme
* never means a removed one.
*/
private function getDirectory(ThemeInterface $theme): ?string
{
$fullPath = $theme->getFullPath();

return $fullPath
? $this->registrar->getPath(ComponentRegistrar::THEME, $fullPath)
: null;
}

private function changePermissions(string $path): bool
{
try {
return $this->filesystemDriver->changePermissions($path, self::READABLE_FILE_MODE);
} catch (FileSystemException $e) {
return false;
}
}

private function isReadable(string $path): bool
{
try {
return $this->filesystemDriver->isReadable($path);
} catch (FileSystemException $e) {
return false;
}
}
}
4 changes: 2 additions & 2 deletions view/adminhtml/templates/config/field/modules.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ $items = $block->getItems();
<?php if (!$items): ?>
<p><?= $escaper->escapeHtml(__('No Swissup modules are installed.')) ?></p>
<?php else: ?>
<table class="swissup-modules-table" data-role="swissup-modules">
<table class="swissup-config-table swissup-modules-table" data-role="swissup-modules">
<thead>
<tr>
<th><?= $escaper->escapeHtml(__('Package')) ?></th>
Expand All @@ -18,7 +18,7 @@ $items = $block->getItems();
<tbody>
<?php foreach ($items as $item): ?>
<tr data-code="<?= $escaper->escapeHtmlAttr($item['code']) ?>"<?= $item['is_outdated'] ? ' class="outdated"' : '' ?>>
<td class="package"><?= $escaper->escapeHtml($item['name']) ?></td>
<td class="package font-mono"><?= $escaper->escapeHtml($item['name']) ?></td>
<td class="version"><?= $escaper->escapeHtml($item['version']) ?></td>
<td class="latest"><span
class="version<?= $item['is_outdated'] ? ' outdated' : '' ?>"
Expand Down
40 changes: 23 additions & 17 deletions view/adminhtml/templates/config/field/virtual_check.phtml
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
<?php /** @var \Swissup\Core\Block\Adminhtml\Config\Form\Field\VirtualCheck $block */ ?>
<?php /** @var \Magento\Framework\Escaper $escaper */ ?>
<style>
#swissup_core_troubleshooting .button-container { margin-top: 20px; }
#swissup_core_troubleshooting .themes-table h4 {padding-left: 0}
#swissup_core_troubleshooting .themes-table table {max-width: 500px}
#swissup_core_troubleshooting .themes-table td { text-align-last: left; }
#swissup_core_troubleshooting .themes-table th { text-align-last: left; }
#swissup_core_troubleshooting .themes-table td.virtual::before {
#swissup_core_troubleshooting:has(.swissup-virtualfix-success) .fix-themes-container { display: none }
#swissup_core_troubleshooting .fix-themes-container { margin-top: 20px; }
#swissup_core_troubleshooting .themes-table .swissup-badge::before {
display:inline-block;
content: '';
width: 15px;
height: 15px;
background-color: red;
width: 10px;
height: 10px;
background-color: #cc1017;
border-radius: 50%;
margin-right: 10px;
margin-right: 6px;
vertical-align: middle;
}
</style>

<h4>Virtual Theme Check</h4>
<?php $themes = $block->getVirtualThemes(); ?>

<?php if (!$block->getVirtualThemes()->getSize()): ?>
<p style="color: green;">No virtual themes found. Check: passed!</p>
<?php if (!$themes): ?>
<p class="swissup-virtualfix-success" style="color: green;">No virtual themes found. Check: passed!</p>
<?php else: ?>
<table>
<table class="swissup-config-table">
<thead>
<tr>
<th scope="col"><?= $escaper->escapeHtml(__('Theme')) ?></th>
<th scope="col"><?= $escaper->escapeHtml(__('Path')) ?></th>
<th scope="col"><?= $escaper->escapeHtml(__('Status')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($block->getVirtualThemes() as $theme): ?>
<?php foreach ($themes as $theme): ?>
<tr>
<td scope="row"><?= $escaper->escapeHtml($theme->getThemeTitle()) ?></td>
<td class="virtual">Virtual</td>
<td><?= $escaper->escapeHtml($theme['title']) ?></td>
<td class="font-mono"><?= $escaper->escapeHtml($theme['path'] ?: '—') ?></td>
<td><span class="swissup-badge"><?= $escaper->escapeHtml($theme['status']) ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
Expand Down
8 changes: 4 additions & 4 deletions view/adminhtml/web/css/system-config/modules.less
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
}
}

.swissup-modules-table {
.swissup-config-table {
thead {
position: sticky;
top: 75px;
Expand All @@ -88,19 +88,19 @@
color: #303030;
font-weight: 600;
}

.package {
.font-mono {
font-family: 'Menlo', 'Consolas', monospace;
}
}

.swissup-modules-table {
.latest .version {
color: #a6a6a6;

&.outdated {
font-weight: 600;
}
}

.links a + a {
margin-left: 1.5rem;
}
Expand Down
Loading