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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0).


## 2026.3.3

### Added

- Override module email templates in a selected project theme, including nested email paths, in PhpStorm and WebStorm [#1205](https://github.com/magento/magento2-phpstorm-plugin/issues/1205).

## 2026.3.2

### Added
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pluginGroup = com.magento.idea.magento2plugin
pluginName = Magento 2 and Adobe Commerce
pluginRepositoryUrl = https://github.com/magento/magento2-phpstorm-plugin
pluginVersion = 2026.3.2
pluginVersion = 2026.3.3
pluginSinceBuild = 262.8665.265
pluginUntilBuild = 268.*
platformType = PS
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

package com.magento.idea.magento2plugin.actions.generation;

import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.psi.PsiFile;
import com.magento.idea.magento2plugin.MagentoIcons;
import com.magento.idea.magento2plugin.actions.generation.dialog.OverrideTemplateInThemeDialog;
import com.magento.idea.magento2plugin.magento.packages.Areas;
import com.magento.idea.magento2plugin.magento.packages.ComponentType;
import com.magento.idea.magento2plugin.util.magento.GetMagentoModuleUtil;
import org.jetbrains.annotations.NotNull;

public class OverrideEmailTemplateInThemeAction extends OverrideFileInThemeAction {

public static final String ACTION_NAME = "Override email template in a project theme";
public static final String ACTION_DESCRIPTION = "Override email template file in project theme";
public static final String EMAIL_DIRECTORY = "email";
public static final String HTML = "html";

public OverrideEmailTemplateInThemeAction() {
super(ACTION_NAME, ACTION_DESCRIPTION, MagentoIcons.MODULE);
}

@Override
public void actionPerformed(final @NotNull AnActionEvent event) {
final Project project = event.getProject();
final PsiFile psiFile = getTargetFile(event);

if (project == null || psiFile == null) {
return;
}
OverrideTemplateInThemeDialog.open(project, psiFile);
}

@Override
protected boolean isOverrideAllowed(
final @NotNull PsiFile file,
final @NotNull Project project
) {
final VirtualFile virtualFile = file.getVirtualFile();

if (virtualFile == null || !HTML.equals(virtualFile.getExtension())) {
return false;
}
final GetMagentoModuleUtil.MagentoModuleData moduleData =
GetMagentoModuleUtil.getByContext(file.getContainingDirectory(), project);

if (moduleData == null || moduleData.getType() != ComponentType.module
|| moduleData.getViewDir() == null) {
return false;
}
final String relativePath = VfsUtilCore.getRelativePath(
virtualFile, moduleData.getViewDir().getVirtualFile(), '/'
);

if (relativePath == null) {
return false;
}
final String[] path = relativePath.split("/");

return path.length >= 3 && EMAIL_DIRECTORY.equals(path[1])
&& (Areas.frontend.toString().equals(path[0])
|| Areas.adminhtml.toString().equals(path[0])
|| Areas.base.toString().equals(path[0]));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.magento.idea.magento2plugin.actions.generation.OverrideTemplateInThemeAction;
import com.magento.idea.magento2plugin.actions.generation.OverrideEmailTemplateInThemeAction;
import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.FieldValidation;
import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.RuleRegistry;
import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.NotEmptyRule;
Expand Down Expand Up @@ -55,7 +56,9 @@ public OverrideTemplateInThemeDialog(
this.psiFile = psiFile;

final String fileType = psiFile.getVirtualFile().getExtension();
if (OverridableFileType.isFilePhtml(fileType)) {
if (OverrideEmailTemplateInThemeAction.HTML.equals(fileType)) {
setTitle(OverrideEmailTemplateInThemeAction.ACTION_DESCRIPTION);
} else if (OverridableFileType.isFilePhtml(fileType)) {
setTitle(OverrideTemplateInThemeAction.ACTION_TEMPLATE_DESCRIPTION);
} else if (OverridableFileType.isFileJS(fileType)) {
setTitle(OverrideTemplateInThemeAction.ACTION_JS_DESCRIPTION);
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
class="com.magento.idea.magento2plugin.actions.generation.OverrideLayoutInThemeAction">
<add-to-group group-id="ProjectViewPopupMenu"/>
</action>
<action id="OverrideEmailTemplateInTheme.Menu"
class="com.magento.idea.magento2plugin.actions.generation.OverrideEmailTemplateInThemeAction">
<add-to-group group-id="ProjectViewPopupMenu"/>
</action>
<action id="OverrideTemplateInTheme.Menu"
class="com.magento.idea.magento2plugin.actions.generation.OverrideTemplateInThemeAction">
<add-to-group group-id="ProjectViewPopupMenu"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

package com.magento.idea.magento2plugin.actions.generation;

import com.intellij.psi.PsiFile;
import com.magento.idea.magento2plugin.BaseProjectTestCase;

public class OverrideEmailTemplateInThemeActionTest extends BaseProjectTestCase {
public void testFrontendEmailIsAllowed() {
assertAllowed("view/frontend/email/shipping.html", true);
}

public void testNestedAdminEmailIsAllowed() {
assertAllowed("view/adminhtml/email/order/shipping.html", true);
}

public void testBaseEmailIsAllowed() {
assertAllowed("view/base/email/shipping.html", true);
}

public void testRegularHtmlTemplateIsNotAllowed() {
assertAllowed("view/frontend/web/template/shipping.html", false);
}

public void testPhpTemplateIsNotAllowed() {
assertAllowed("view/frontend/email/shipping.phtml", false);
}

public void testUnrelatedEmailDirectoryIsNotAllowed() {
assertAllowed("frontend/email/shipping.html", false);
}

public void testUnsupportedAreaIsNotAllowed() {
assertAllowed("view/graphql/email/shipping.html", false);
}

public void testHtmlOutsideModuleIsNotAllowed() {
final PsiFile file = myFixture.addFileToProject("misc/view/frontend/email/shipping.html", "<p>Hello</p>");
assertFalse(new OverrideEmailTemplateInThemeAction().isOverrideAllowed(file, getProject()));
}

private void assertAllowed(final String relativePath, final boolean expected) {
// Resolve the module through module.xml, without registration.php or PHP PSI.
myFixture.addFileToProject(
"vendor/example/email-module/etc/module.xml",
"<config><module name=\"Example_Email\"/></config>"
);
final PsiFile file = myFixture.addFileToProject(
"vendor/example/email-module/" + relativePath, "<p>Hello {{var name}}</p>"
);
assertEquals(expected, new OverrideEmailTemplateInThemeAction().isOverrideAllowed(file, getProject()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

package com.magento.idea.magento2plugin.actions

import com.intellij.driver.sdk.ui.components.common.ideFrame
import com.intellij.driver.sdk.ui.components.elements.JComboBoxUiComponent
import com.intellij.driver.sdk.ui.components.elements.dialog
import com.magento.idea.magento2plugin.ui.assertGeneratedFile
import com.magento.idea.magento2plugin.ui.clickOkButton
import com.magento.idea.magento2plugin.ui.invokeProjectViewAction
import com.magento.idea.magento2plugin.ui.runWebStormUiTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.nio.file.Files

class OverrideEmailTemplateInThemeActionUiTest {
@Test
fun `copies nested email into theme`() =

Check notice on line 21 in src/uiTest/kotlin/com/magento/idea/magento2plugin/actions/OverrideEmailTemplateInThemeActionUiTest.kt

View workflow job for this annotation

GitHub Actions / static-tests

Function naming convention

Function name ```copies nested email into theme``` may contain only letters and digits
runWebStormUiTest(
FILE_GENERATORS_FIXTURE,
"app/design/frontend/Acme/storefront/Acme_Shipping/email/order/shipping.html",
) { projectPath ->
val sourcePath = projectPath.resolve(
"app/code/Acme/Shipping/view/frontend/email/order/shipping.html",
)
val originalText = Files.readString(sourcePath)
invokeProjectViewAction(
MODULE_PATH + listOf("view", "frontend", "email", "order", "shipping.html"),
"OverrideEmailTemplateInTheme.Menu",
waitForAction = false,
)

ideFrame {
dialog(title = "Override email template file in project theme") {
val theme = xx(
"//div[@class='JComboBox']",
JComboBoxUiComponent::class.java,
).list().single()
theme.selectItem("frontend/Acme/storefront")
clickOkButton()
}
}

assertGeneratedFile(
projectPath,
"app/design/frontend/Acme/storefront/Acme_Shipping/email/order/shipping.html",
"{{var order.increment_id}}",
)
assertEquals(
originalText,
Files.readString(projectPath.resolve(
"app/design/frontend/Acme/storefront/Acme_Shipping/email/order/shipping.html",
)),
)
assertEquals(originalText, Files.readString(sourcePath))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!--@subject Shipping update @-->
<p>Order {{var order.increment_id}} has shipped.</p>
Loading