diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 9eceb7a28..48ccf6142 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -56,6 +56,15 @@ + + + + + + + + + diff --git a/resources/fileTemplates/internal/Magento Routes XML.xml.ft b/resources/fileTemplates/internal/Magento Routes XML.xml.ft index 55d92b380..c850c6c6a 100644 --- a/resources/fileTemplates/internal/Magento Routes XML.xml.ft +++ b/resources/fileTemplates/internal/Magento Routes XML.xml.ft @@ -1,6 +1,11 @@ #parse("XML File Header.xml") - - + + #if (${HAS_ROUTE_DATA}) + + + +#end diff --git a/resources/fileTemplates/internal/Magento Routes XML.xml.html b/resources/fileTemplates/internal/Magento Routes XML.xml.html index 28926142d..742a5aa64 100644 --- a/resources/fileTemplates/internal/Magento Routes XML.xml.html +++ b/resources/fileTemplates/internal/Magento Routes XML.xml.html @@ -30,7 +30,7 @@ ${ROUTER_ID}   - specifies the name of the router in Magento. + Specifies the name of the router in Magento. See the reference tables in the Router class section @@ -38,6 +38,35 @@ + + ${HAS_ROUTE_DATA} +   + Technical value used to enable/disable route information rendering + + + + + ${ROUTE_ID} +  Specifies the unique node id for this route in Magento, + is also the first segment of its associated layout handle XML filename: routeId_controller_action.xml + + + + + + ${FRONT_NAME} +   + Specifies the first segment after the base URL of a request + + + + + ${MODULE_NAME} +   + Specifies the name of your module + + + - \ No newline at end of file + diff --git a/src/com/magento/idea/magento2plugin/actions/context/AbstractContextAction.java b/src/com/magento/idea/magento2plugin/actions/context/AbstractContextAction.java new file mode 100644 index 000000000..91c835645 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/context/AbstractContextAction.java @@ -0,0 +1,161 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.context; + +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.actions.AttributesDefaults; +import com.intellij.ide.fileTemplates.actions.CreateFromTemplateActionBase; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.impl.SimpleDataContext; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.MagentoIcons; +import com.magento.idea.magento2plugin.magento.files.ModuleFileInterface; +import com.magento.idea.magento2plugin.util.magento.GetMagentoModuleUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractContextAction extends CreateFromTemplateActionBase { + + private static final DataKey ATTRIBUTE_DEFAULTS = DataKey.create( + "attribute.defaults" + ); + private final ModuleFileInterface moduleFile; + private DataContext customDataContext; + + /** + * Abstract context action constructor. + * + * @param title String + * @param description String + * @param moduleFile ModuleFileInterface + */ + public AbstractContextAction( + final @NotNull String title, + final @NotNull String description, + final @NotNull ModuleFileInterface moduleFile + ) { + super(title, description, MagentoIcons.MODULE); + this.moduleFile = moduleFile; + } + + @Override + public void update(final @NotNull AnActionEvent event) { + event.getPresentation().setEnabled(false); + event.getPresentation().setVisible(false); + + final Project project = event.getProject(); + + if (project == null) { + return; + } + final DataContext context = event.getDataContext(); + final PsiElement targetElement = LangDataKeys.PSI_ELEMENT.getData(context); + + if (targetElement == null) { + return; + } + PsiDirectory targetDirectory = null; + PsiFile targetFile = null; + + if (targetElement instanceof PsiDirectory) { + targetDirectory = (PsiDirectory) targetElement; + } else if (targetElement instanceof PsiFile) { + targetFile = (PsiFile) targetElement; + targetDirectory = targetFile.getContainingDirectory(); + } + + if (targetDirectory == null) { + return; + } + final GetMagentoModuleUtil.MagentoModuleData moduleData = GetMagentoModuleUtil + .getByContext(targetDirectory, project); + + if (moduleData == null + || moduleData.getName() == null + || !isVisible(moduleData, targetDirectory, targetFile)) { + return; + } + customDataContext = SimpleDataContext + .builder() + .add( + ATTRIBUTE_DEFAULTS, + getProperties( + new AttributesDefaults(), + moduleData, + targetDirectory, + targetFile + ) + ) + .build(); + + event.getPresentation().setEnabled(true); + event.getPresentation().setVisible(true); + } + + /** + * Implement check if an action should be shown in the context defined by the module, + * target directory or target file. + * + * @param moduleData GetMagentoModuleUtil.MagentoModuleData + * @param targetDirectory PsiDirectory + * @param targetFile PsiFile + * + * @return boolean + */ + protected abstract boolean isVisible( + final @NotNull GetMagentoModuleUtil.MagentoModuleData moduleData, + final PsiDirectory targetDirectory, + final PsiFile targetFile + ); + + /** + * Implement to populate used in the target template variables. + * + * @param defaults AttributesDefaults + * @param moduleData GetMagentoModuleUtil.MagentoModuleData + * @param targetDirectory PsiDirectory + * @param targetFile PsiFile + * + * @return AttributesDefaults + */ + protected abstract AttributesDefaults getProperties( + final @NotNull AttributesDefaults defaults, + final @NotNull GetMagentoModuleUtil.MagentoModuleData moduleData, + final PsiDirectory targetDirectory, + final PsiFile targetFile + ); + + @Override + protected @Nullable AttributesDefaults getAttributesDefaults( + final @NotNull DataContext dataContext + ) { + return customDataContext.getData(ATTRIBUTE_DEFAULTS); + } + + @Override + protected FileTemplate getTemplate( + final @NotNull Project project, + final @NotNull PsiDirectory directory + ) { + final FileTemplateManager manager = FileTemplateManager.getInstance(project); + final FileTemplate template = manager.findInternalTemplate(moduleFile.getTemplate()); + template.setFileName(moduleFile.getFileName()); + + return template; + } + + @Override + public boolean isDumbAware() { + return false; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/context/xml/NewRoutesXmlAction.java b/src/com/magento/idea/magento2plugin/actions/context/xml/NewRoutesXmlAction.java new file mode 100644 index 000000000..cd007dffe --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/context/xml/NewRoutesXmlAction.java @@ -0,0 +1,68 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.context.xml; + +import com.intellij.ide.fileTemplates.actions.AttributesDefaults; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.actions.context.AbstractContextAction; +import com.magento.idea.magento2plugin.magento.files.RoutesXml; +import com.magento.idea.magento2plugin.magento.packages.Areas; +import com.magento.idea.magento2plugin.magento.packages.ComponentType; +import com.magento.idea.magento2plugin.magento.packages.Package; +import com.magento.idea.magento2plugin.util.magento.GetMagentoModuleUtil; +import java.util.Arrays; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +public class NewRoutesXmlAction extends AbstractContextAction { + + public static final String ACTION_NAME = "Magento 2 Routes File"; + public static final String ACTION_DESCRIPTION = "Create a new Magento 2 routes.xml file"; + + /** + * New routes.xml file generation action constructor. + */ + public NewRoutesXmlAction() { + super(ACTION_NAME, ACTION_DESCRIPTION, new RoutesXml()); + } + + @Override + protected boolean isVisible( + final @NotNull GetMagentoModuleUtil.MagentoModuleData moduleData, + final @NotNull PsiDirectory targetDirectory, + final PsiFile targetFile + ) { + final List allowedDirectories = Arrays.asList( + Package.moduleBaseAreaDir, + Areas.adminhtml.toString(), + Areas.frontend.toString() + ); + + return allowedDirectories.contains(targetDirectory.getName()) + && moduleData.getType().equals(ComponentType.module); + } + + @Override + protected AttributesDefaults getProperties( + final @NotNull AttributesDefaults defaults, + final @NotNull GetMagentoModuleUtil.MagentoModuleData moduleData, + final PsiDirectory targetDirectory, + final PsiFile targetFile + ) { + defaults.addPredefined("HAS_ROUTE_DATA", String.valueOf(true)); + defaults.addPredefined("MODULE_NAME", moduleData.getName()); + + if (Areas.adminhtml.toString().equals(targetDirectory.getName())) { + defaults.addPredefined("ROUTER_ID", RoutesXml.ROUTER_ID_ADMIN); + } else if (Package.moduleBaseAreaDir.equals(targetDirectory.getName()) + || Areas.frontend.toString().equals(targetDirectory.getName())) { + defaults.addPredefined("ROUTER_ID", RoutesXml.ROUTER_ID_STANDARD); + } + + return defaults; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/OverrideInThemeAction.java b/src/com/magento/idea/magento2plugin/actions/generation/OverrideInThemeAction.java index c717c0a79..85a77e67a 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/OverrideInThemeAction.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/OverrideInThemeAction.java @@ -21,12 +21,13 @@ import org.jetbrains.annotations.NotNull; public class OverrideInThemeAction extends DumbAwareAction { - public static String actionName = "Override this template in a project theme"; - public static String actionDescription = "Override in project theme"; + + public static final String ACTION_NAME = "Override this template in a project theme"; + public static final String ACTION_DESCRIPTION = "Override in project theme"; private PsiFile psiFile; public OverrideInThemeAction() { - super(actionName, actionDescription, MagentoIcons.MODULE); + super(ACTION_NAME, ACTION_DESCRIPTION, MagentoIcons.MODULE); } /** diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/OverrideInThemeDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/OverrideInThemeDialog.java index 1e3133b53..01d11059c 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/dialog/OverrideInThemeDialog.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/OverrideInThemeDialog.java @@ -58,7 +58,7 @@ public OverrideInThemeDialog(final @NotNull Project project, final PsiFile psiFi setContentPane(contentPane); setModal(true); - setTitle(OverrideInThemeAction.actionDescription); + setTitle(OverrideInThemeAction.ACTION_DESCRIPTION); getRootPane().setDefaultButton(buttonOK); fillThemeOptions(); diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGenerator.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGenerator.java index 8f345d496..2c388984e 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGenerator.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGenerator.java @@ -49,6 +49,7 @@ public RoutesXmlGenerator( * @return PsiFile */ @Override + @SuppressWarnings("PMD.CognitiveComplexity") public PsiFile generate(final String actionName) { final XmlFile routesXml = (XmlFile) findOrCreateRoutesXml.execute( actionName, @@ -71,8 +72,8 @@ public PsiFile generate(final String actionName) { routerTag.setAttribute( "id", routesXmlData.getArea().equals(Areas.frontend.toString()) - ? RoutesXml.routerIdStandart - : RoutesXml.routerIdAdmin + ? RoutesXml.ROUTER_ID_STANDARD + : RoutesXml.ROUTER_ID_ADMIN ); } @NotNull final XmlTag[] buttonsTags = routerTag.findSubTags("route"); diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateRoutesXml.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateRoutesXml.java index 426cd256f..057646a2e 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateRoutesXml.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/FindOrCreateRoutesXml.java @@ -73,8 +73,8 @@ private Areas getArea(final String area) { private Properties getAttributes(final String area) { final Properties attributes = new Properties(); attributes.setProperty("ROUTER_ID", area.equals(Areas.frontend.toString()) - ? RoutesXml.routerIdStandart - : RoutesXml.routerIdAdmin + ? RoutesXml.ROUTER_ID_STANDARD + : RoutesXml.ROUTER_ID_ADMIN ); return attributes; } diff --git a/src/com/magento/idea/magento2plugin/magento/files/RoutesXml.java b/src/com/magento/idea/magento2plugin/magento/files/RoutesXml.java index ed83330f9..d66a0e0ce 100644 --- a/src/com/magento/idea/magento2plugin/magento/files/RoutesXml.java +++ b/src/com/magento/idea/magento2plugin/magento/files/RoutesXml.java @@ -9,19 +9,20 @@ import com.intellij.lang.xml.XMLLanguage; public class RoutesXml implements ModuleFileInterface { - public static String fileName = "routes.xml"; - public static String template = "Magento Routes XML"; - public static String routerIdStandart = "standart"; - public static String routerIdAdmin = "admin"; + + public static final String FILE_NAME = "routes.xml"; + public static final String TEMPLATE = "Magento Routes XML"; + public static final String ROUTER_ID_STANDARD = "standard"; + public static final String ROUTER_ID_ADMIN = "admin"; @Override public String getFileName() { - return fileName; + return FILE_NAME; } @Override public String getTemplate() { - return template; + return TEMPLATE; } @Override diff --git a/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java b/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java index a5ad795e8..edcfa91bd 100644 --- a/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java +++ b/src/com/magento/idea/magento2plugin/magento/packages/ComponentType.java @@ -5,8 +5,27 @@ package com.magento.idea.magento2plugin.magento.packages; +import org.jetbrains.annotations.NotNull; + @SuppressWarnings({"PMD.FieldNamingConventions"}) -public enum ComponentType { +public enum ComponentType { module, - theme + theme; + + /** + * Get component type by value. + * + * @param value String + * + * @return ComponentType + */ + public static ComponentType getByValue(final @NotNull String value) { + for (final ComponentType type : ComponentType.values()) { + if (value.equals(type.toString())) { + return type; + } + } + + return null; + } } diff --git a/src/com/magento/idea/magento2plugin/util/magento/GetMagentoModuleUtil.java b/src/com/magento/idea/magento2plugin/util/magento/GetMagentoModuleUtil.java new file mode 100644 index 000000000..067191935 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/util/magento/GetMagentoModuleUtil.java @@ -0,0 +1,144 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.util.magento; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.php.lang.psi.elements.ClassConstantReference; +import com.jetbrains.php.lang.psi.elements.MethodReference; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import com.jetbrains.php.lang.psi.elements.impl.ClassConstImpl; +import com.magento.idea.magento2plugin.magento.files.RegistrationPhp; +import com.magento.idea.magento2plugin.magento.packages.ComponentType; +import java.util.Collection; +import org.jetbrains.annotations.NotNull; + +public final class GetMagentoModuleUtil { + + private GetMagentoModuleUtil() {} + + /** + * Get module component by context. + * + * @param psiDirectory PsiDirectory + * @param project Project + * + * @return MagentoModuleData + */ + @SuppressWarnings("PMD.AvoidBranchingStatementAsLastInLoop") + public static MagentoModuleData getByContext( + final @NotNull PsiDirectory psiDirectory, + final @NotNull Project project + ) { + final String basePath = project.getBasePath(); + + if (basePath == null) { + return null; + } + final PsiFile registrationFile = getModuleRegistrationFile(psiDirectory, basePath); + + if (registrationFile == null) { + return null; + } + final Collection methodReferences = PsiTreeUtil.findChildrenOfType( + registrationFile, + MethodReference.class + ); + + for (final MethodReference methodReference : methodReferences) { + if (!RegistrationPhp.REGISTER_METHOD_NAME.equals(methodReference.getName())) { + continue; + } + final PsiElement[] parameters = methodReference.getParameters(); + final PsiElement typeHolder = parameters[0]; + final PsiElement nameHolder = parameters[1]; + + final String type = parseParameterValue(typeHolder); + final String name = parseParameterValue(nameHolder); + + if (name == null || type == null) { + return null; + } + final ComponentType resolvedType = ComponentType.getByValue(type); + + if (resolvedType == null) { + return null; + } + + return new MagentoModuleData(name, resolvedType); + } + + return null; + } + + private static PsiFile getModuleRegistrationFile( + final @NotNull PsiDirectory directory, + final @NotNull String basePath + ) { + if (basePath.equals(directory.getVirtualFile().getPath())) { + return null; + } + final PsiFile registration = directory.findFile(RegistrationPhp.FILE_NAME); + + if (registration != null) { + return registration; + } + final PsiDirectory parentDirectory = directory.getParentDirectory(); + + if (parentDirectory == null) { + return null; + } + + return getModuleRegistrationFile(parentDirectory, basePath); + } + + private static String parseParameterValue(final PsiElement valueHolder) { + if (valueHolder instanceof ClassConstantReference) { + final PsiElement resolved = ((ClassConstantReference) valueHolder).resolve(); + + if (!(resolved instanceof ClassConstImpl)) { + return null; + } + final ClassConstImpl resolvedConstant = (ClassConstImpl) resolved; + final PsiElement value = resolvedConstant.getDefaultValue(); + + if (value == null) { + return null; + } + + return parseParameterValue(value); + } else if (valueHolder instanceof StringLiteralExpression) { + return ((StringLiteralExpression) valueHolder).getContents(); + } + + return null; + } + + public static class MagentoModuleData { + + private final String name; + private final ComponentType type; + + public MagentoModuleData( + final @NotNull String name, + final @NotNull ComponentType type + ) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public ComponentType getType() { + return type; + } + } +} diff --git a/tests/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGeneratorTest.java b/tests/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGeneratorTest.java index 892c7564a..4cae3306d 100644 --- a/tests/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGeneratorTest.java +++ b/tests/com/magento/idea/magento2plugin/actions/generation/generator/RoutesXmlGeneratorTest.java @@ -12,6 +12,7 @@ import com.magento.idea.magento2plugin.magento.packages.Areas; public class RoutesXmlGeneratorTest extends BaseGeneratorTestCase { + private static final String EXPECTED_DIRECTORY = "src/app/code/Foo/Bar/etc/adminhtml"; private static final String MODULE_NAME = "Foo_Bar"; private static final String ROUTE = "customroute"; @@ -20,7 +21,7 @@ public class RoutesXmlGeneratorTest extends BaseGeneratorTestCase { * Test generating routes XML file. */ public void testGenerateRoutesXmlFile() { - final String filePath = this.getFixturePath(RoutesXml.fileName); + final String filePath = this.getFixturePath(RoutesXml.FILE_NAME); final PsiFile expectedFile = myFixture.configureByFile(filePath); final Project project = myFixture.getProject(); final RoutesXmlData routesXmlData = new RoutesXmlData(