diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 5bca77ca9..04e5261d4 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -78,7 +78,8 @@ - + + diff --git a/resources/fileTemplates/internal/Magento Module Setup Patch File.php.ft b/resources/fileTemplates/internal/Magento Module Setup Patch File.php.ft new file mode 100644 index 000000000..37885e515 --- /dev/null +++ b/resources/fileTemplates/internal/Magento Module Setup Patch File.php.ft @@ -0,0 +1,68 @@ +moduleDataSetup = $moduleDataSetup; + } + + /** + * Do Upgrade. + * + * @return void + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + + // TODO: The code that you want apply in the patch + + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * Get aliases (previous names) for the patch. + * + * @return string[] + */ + public function getAliases() + { + return []; + } + + /** + * Get array of patches that have to be executed prior to this. + * + * Example of implementation: + * + * [ + * \Vendor_Name\Module_Name\Setup\Patch\Patch1::class, + * \Vendor_Name\Module_Name\Setup\Patch\Patch2::class + * ] + * + * @return string[] + */ + public static function getDependencies() + { + return []; + } +} diff --git a/resources/fileTemplates/internal/Magento Module Setup Patch File.php.html b/resources/fileTemplates/internal/Magento Module Setup Patch File.php.html new file mode 100644 index 000000000..f1be39dee --- /dev/null +++ b/resources/fileTemplates/internal/Magento Module Setup Patch File.php.html @@ -0,0 +1,38 @@ + + + + + + + + + + +
+ A data patch is a class that contains data modification instructions. +
+
+ Read more About the data and schema patches in the + + DevDocs. +
+
+ + + + + + + + + +
Predefined variables explanation:
${CLASS_NAME} Specifies the name of your class + +
+ + diff --git a/src/com/magento/idea/magento2plugin/actions/context/php/NewSetupDataPatchAction.java b/src/com/magento/idea/magento2plugin/actions/context/php/NewSetupDataPatchAction.java new file mode 100644 index 000000000..f2ebfe81b --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/context/php/NewSetupDataPatchAction.java @@ -0,0 +1,90 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.context.php; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.actions.context.CustomGeneratorContextAction; +import com.magento.idea.magento2plugin.actions.generation.dialog.NewSetupDataPatchDialog; +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 org.jetbrains.annotations.NotNull; + +public class NewSetupDataPatchAction extends CustomGeneratorContextAction { + + public static final String ACTION_NAME = "Magento 2 Setup Data Patch"; + public static final String ACTION_DESCRIPTION = "Create a new Magento 2 Setup Data Patch"; + public static final String SETUP_DIRECTORY = "Setup"; + public static final String PATCH_DIRECTORY = "Patch"; + public static final String DATA_DIRECTORY = "Data"; + + public NewSetupDataPatchAction() { + super(ACTION_NAME, ACTION_DESCRIPTION); + } + + @Override + public void actionPerformed(final @NotNull AnActionEvent event) { + final GetMagentoModuleUtil.MagentoModuleData moduleData = getModuleData(); + + if (event.getProject() == null || moduleData == null || getDirectory() == null) { + return; + } + final String[] module = moduleData.getName().split(Package.vendorModuleNameSeparator); + + if (module.length != 2) { //NOPMD + return; + } + final PsiDirectory rootDirectory = moduleData.getModuleDir().findSubdirectory( + SETUP_DIRECTORY + ); + + if (rootDirectory == null) { + return; + } + NewSetupDataPatchDialog.open(event.getProject(), rootDirectory, module[0], module[1]); + } + + @Override + protected boolean isVisible( + final @NotNull GetMagentoModuleUtil.MagentoModuleData moduleData, + final PsiDirectory targetDirectory, + final PsiFile targetFile + ) { + if (!moduleData.getType().equals(ComponentType.module)) { + return false; + } + + if (SETUP_DIRECTORY.equals(targetDirectory.getName())) { + return moduleData.getModuleDir().equals(targetDirectory.getParentDirectory()); + } + + if (PATCH_DIRECTORY.equals(targetDirectory.getName())) { + final PsiDirectory setupDirCandidate = targetDirectory.getParentDirectory(); + + return setupDirCandidate != null + && SETUP_DIRECTORY.equals(setupDirCandidate.getName()) + && moduleData.getModuleDir().equals(setupDirCandidate.getParentDirectory()); + } + + if (DATA_DIRECTORY.equals(targetDirectory.getName())) { + final PsiDirectory patchDirCandidate = targetDirectory.getParentDirectory(); + + if (patchDirCandidate == null) { + return false; + } + final PsiDirectory setupDirCandidate = patchDirCandidate.getParentDirectory(); + + return setupDirCandidate != null + && PATCH_DIRECTORY.equals(patchDirCandidate.getName()) + && SETUP_DIRECTORY.equals(setupDirCandidate.getName()) + && moduleData.getModuleDir().equals(setupDirCandidate.getParentDirectory()); + } + + return false; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/ModuleSetupDataPatchData.java b/src/com/magento/idea/magento2plugin/actions/generation/ModuleSetupDataPatchData.java new file mode 100644 index 000000000..e24874cca --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/ModuleSetupDataPatchData.java @@ -0,0 +1,52 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation; + +import com.intellij.psi.PsiDirectory; +import org.jetbrains.annotations.NotNull; + +public class ModuleSetupDataPatchData { + + private final String packageName; + private final String moduleName; + private final PsiDirectory baseDir; + private final String className; + + /** + * Constructor. + * + * @param packageName String + * @param moduleName String + * @param baseDir PsiDirectory + */ + public ModuleSetupDataPatchData( + final @NotNull String packageName, + final @NotNull String moduleName, + final @NotNull PsiDirectory baseDir, + final @NotNull String className + ) { + this.packageName = packageName; + this.moduleName = moduleName; + this.baseDir = baseDir; + this.className = className; + } + + public @NotNull String getPackageName() { + return packageName; + } + + public @NotNull String getModuleName() { + return moduleName; + } + + public @NotNull PsiDirectory getBaseDir() { + return baseDir; + } + + public @NotNull String getClassName() { + return className; + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.java index 178b97d98..096945643 100644 --- a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.java +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.java @@ -17,6 +17,7 @@ import com.magento.idea.magento2plugin.actions.generation.ModuleObserverData; import com.magento.idea.magento2plugin.actions.generation.data.ObserverEventsXmlData; import com.magento.idea.magento2plugin.actions.generation.data.ui.ComboBoxItemData; +import com.magento.idea.magento2plugin.actions.generation.dialog.reflection.GetReflectionFieldUtil; 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.DirectoryRule; @@ -37,6 +38,7 @@ import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -239,15 +241,17 @@ protected void onOK() { private boolean validateFields() { final PsiFile[] directoryFiles = getDirectoryFiles(baseDir); + final Field classNameField = GetReflectionFieldUtil.getByName("className", this.getClass()); - if (directoryFiles != null) { + if (directoryFiles != null && classNameField != null) { for (final PsiFile file : directoryFiles) { final String className = ModuleObserverFile.resolveClassNameFromInput( getClassName() ); + if (file.getName().equals(className + ModuleObserverFile.EXTENSION)) { showErrorMessage( - fieldsValidationsList.get(1).getField(), + classNameField, "Class name " + className + " already exist." ); @@ -255,11 +259,16 @@ private boolean validateFields() { } } } - if (!getDirectoryStructure().isEmpty() + final Field directoryStructureField = GetReflectionFieldUtil.getByName( + "directoryStructure", + this.getClass() + ); + + if (!getDirectoryStructure().isEmpty() && directoryStructureField != null && !DirectoryRule.getInstance().check(getDirectoryStructure()) ) { showErrorMessage( - this.getClass().getDeclaredFields()[11], + directoryStructureField, "The Directory Path field does not contain a valid directory." ); diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewSetupDataPatchDialog.form b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewSetupDataPatchDialog.form new file mode 100644 index 000000000..f9879145a --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewSetupDataPatchDialog.form @@ -0,0 +1,90 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewSetupDataPatchDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewSetupDataPatchDialog.java new file mode 100644 index 000000000..eb23df3a1 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewSetupDataPatchDialog.java @@ -0,0 +1,184 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.actions.context.php.NewSetupDataPatchAction; +import com.magento.idea.magento2plugin.actions.generation.ModuleSetupDataPatchData; +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; +import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.PhpClassRule; +import com.magento.idea.magento2plugin.actions.generation.generator.ModuleSetupDataPatchGenerator; +import com.magento.idea.magento2plugin.actions.generation.generator.util.DirectoryGenerator; +import com.magento.idea.magento2plugin.magento.files.ModuleSetupDataPatchFile; +import com.magento.idea.magento2plugin.magento.packages.File; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import org.jetbrains.annotations.NotNull; + +public class NewSetupDataPatchDialog extends AbstractDialog { + + private static final String CLASS_NAME = "Class Name"; + private final Project project; + private final PsiDirectory baseDir; + private final String moduleName; + private final String modulePackage; + + private JPanel contentPanel; + private JButton buttonOK; + private JButton buttonCancel; + + @FieldValidation(rule = RuleRegistry.NOT_EMPTY, + message = {NotEmptyRule.MESSAGE, CLASS_NAME}) + @FieldValidation(rule = RuleRegistry.PHP_CLASS, + message = {PhpClassRule.MESSAGE, CLASS_NAME}) + private JTextField className; + private JLabel classNameLabel;//NOPMD + private JLabel classNameErrorMessage;//NOPMD + + + /** + * Constructor. + * + * @param project Project + * @param directory PsiDirectory + */ + public NewSetupDataPatchDialog( + final @NotNull Project project, + final @NotNull PsiDirectory directory, + final String modulePackage, + final String moduleName + ) { + super(); + + this.project = project; + this.baseDir = directory; + this.modulePackage = modulePackage; + this.moduleName = moduleName; + + setContentPane(contentPanel); + setModal(true); + setTitle(NewSetupDataPatchAction.ACTION_DESCRIPTION); + getRootPane().setDefaultButton(buttonOK); + + buttonOK.addActionListener((final ActionEvent event) -> onOK()); + buttonCancel.addActionListener((final ActionEvent event) -> onCancel()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent event) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPanel.registerKeyboardAction( + (final ActionEvent event) -> onCancel(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + ); + + addComponentListener( + new FocusOnAFieldListener(() -> className.requestFocusInWindow()) + ); + } + + /** + * Open dialog. + * + * @param project Project + * @param directory PsiDirectory + */ + public static void open( + final @NotNull Project project, + final @NotNull PsiDirectory directory, + final String modulePackage, + final String moduleName + ) { + final NewSetupDataPatchDialog dialog = new NewSetupDataPatchDialog( + project, + directory, + modulePackage, + moduleName + ); + dialog.pack(); + dialog.centerDialog(dialog); + dialog.setVisible(true); + } + + protected void onOK() { + if (validateFields()) { + generateFile(); + exit(); + } + } + + private void generateFile() { + final PsiDirectory directory = DirectoryGenerator.getInstance().findOrCreateSubdirectories( + baseDir, + NewSetupDataPatchAction.PATCH_DIRECTORY + File.separator + + NewSetupDataPatchAction.DATA_DIRECTORY + ); + final ModuleSetupDataPatchGenerator generator = new ModuleSetupDataPatchGenerator( + new ModuleSetupDataPatchData( + modulePackage, + moduleName, + directory, + getClassName() + ), + project + ); + + generator.generate(NewSetupDataPatchAction.ACTION_NAME, true); + } + + public String getClassName() { + return className.getText().trim(); + } + + private boolean validateFields() { + final PsiDirectory patchDirectory = baseDir.findSubdirectory( + NewSetupDataPatchAction.PATCH_DIRECTORY + ); + PsiDirectory directory = null; + + if (patchDirectory != null) { + directory = patchDirectory.findSubdirectory(NewSetupDataPatchAction.DATA_DIRECTORY); + } + + if (directory != null) { + for (final PsiFile file : directory.getFiles()) { + final String className = ModuleSetupDataPatchFile.resolveClassNameFromInput( + getClassName() + ); + + if (file.getName().equals(className + ModuleSetupDataPatchFile.EXTENSION)) { + showErrorMessage( + fieldsValidationsList.get(0).getField(), + "Class name `" + className + "` already exist." + ); + + return false; + } + } + } + + return validateFormFields(); + } +} diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/ModuleSetupDataPatchGenerator.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/ModuleSetupDataPatchGenerator.java new file mode 100644 index 000000000..531e43a32 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/ModuleSetupDataPatchGenerator.java @@ -0,0 +1,68 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.generator; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.actions.generation.ModuleSetupDataPatchData; +import com.magento.idea.magento2plugin.actions.generation.generator.util.FileFromTemplateGenerator; +import com.magento.idea.magento2plugin.magento.files.ModuleSetupDataPatchFile; +import java.util.Properties; +import org.jetbrains.annotations.NotNull; + +public class ModuleSetupDataPatchGenerator extends FileGenerator { + + private final ModuleSetupDataPatchData moduleSetupPatchData; + private final FileFromTemplateGenerator fileFromTemplateGenerator; + + /** + * Construct generator. + * + * @param moduleSetupPatchData ModuleSetupPatchData + * @param project Project + */ + public ModuleSetupDataPatchGenerator( + final @NotNull ModuleSetupDataPatchData moduleSetupPatchData, + final Project project + ) { + super(project); + this.moduleSetupPatchData = moduleSetupPatchData; + this.fileFromTemplateGenerator = new FileFromTemplateGenerator(project); + } + + /** + * Generate file. + * + * @param actionName String + * + * @return PsiFile + */ + @Override + public PsiFile generate(final String actionName) { + return fileFromTemplateGenerator.generate( + new ModuleSetupDataPatchFile(moduleSetupPatchData.getClassName()), + getAttributes(), + moduleSetupPatchData.getBaseDir(), + actionName + ); + } + + /** + * Fill template properties. + * + * @param attributes Properties + */ + @Override + protected void fillAttributes(final Properties attributes) { + attributes.setProperty("CLASS_NAME", ModuleSetupDataPatchFile.resolveClassNameFromInput( + moduleSetupPatchData.getClassName() + )); + attributes.setProperty( + "MODULE_NAME", + moduleSetupPatchData.getPackageName() + "\\" + moduleSetupPatchData.getModuleName() + ); + } +} diff --git a/src/com/magento/idea/magento2plugin/magento/files/ModuleSetupDataPatchFile.java b/src/com/magento/idea/magento2plugin/magento/files/ModuleSetupDataPatchFile.java new file mode 100644 index 000000000..6a553adc9 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/magento/files/ModuleSetupDataPatchFile.java @@ -0,0 +1,64 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.magento.files; + +import com.intellij.lang.Language; +import com.jetbrains.php.lang.PhpLanguage; +import org.jetbrains.annotations.NotNull; + +public class ModuleSetupDataPatchFile implements ModuleFileInterface { + + public static final String FILE_SUFFIX = "Patch"; + public static final String EXTENSION = ".php"; + public static final String TEMPLATE = "Magento Module Setup Patch File"; + private final String className; + + /** + * Constructor. + * + * @param className String + */ + public ModuleSetupDataPatchFile( + final @NotNull String className + ) { + this.className = resolveClassNameFromInput(className); + } + + /** + * Resolve class name from user input. + * + * @param input String + * + * @return String + */ + public static String resolveClassNameFromInput(final String input) { + if (input.length() <= FILE_SUFFIX.length()) { + return input + FILE_SUFFIX; + } + final String suffix = input.substring(input.length() - FILE_SUFFIX.length()); + + if (FILE_SUFFIX.equals(suffix)) { + return input; + } else { + return input + FILE_SUFFIX; + } + } + + @Override + public String getFileName() { + return className + EXTENSION; + } + + @Override + public String getTemplate() { + return TEMPLATE; + } + + @Override + public Language getLanguage() { + return PhpLanguage.INSTANCE; + } +} diff --git a/testData/actions/generation/generator/ModuleSetupDataPatchGenerator/generateModuleSetupDataPatchFile/TestClassPatch.php b/testData/actions/generation/generator/ModuleSetupDataPatchGenerator/generateModuleSetupDataPatchFile/TestClassPatch.php new file mode 100644 index 000000000..f220c92c0 --- /dev/null +++ b/testData/actions/generation/generator/ModuleSetupDataPatchGenerator/generateModuleSetupDataPatchFile/TestClassPatch.php @@ -0,0 +1,68 @@ +moduleDataSetup = $moduleDataSetup; + } + + /** + * Do Upgrade. + * + * @return void + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + + // TODO: The code that you want apply in the patch + + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * Get aliases (previous names) for the patch. + * + * @return string[] + */ + public function getAliases() + { + return []; + } + + /** + * Get array of patches that have to be executed prior to this. + * + * Example of implementation: + * + * [ + * \Vendor_Name\Module_Name\Setup\Patch\Patch1::class, + * \Vendor_Name\Module_Name\Setup\Patch\Patch2::class + * ] + * + * @return string[] + */ + public static function getDependencies() + { + return []; + } +} diff --git a/tests/com/magento/idea/magento2plugin/actions/generation/generator/ModuleSetupDataPatchGeneratorTest.java b/tests/com/magento/idea/magento2plugin/actions/generation/generator/ModuleSetupDataPatchGeneratorTest.java new file mode 100644 index 000000000..956f1ebf2 --- /dev/null +++ b/tests/com/magento/idea/magento2plugin/actions/generation/generator/ModuleSetupDataPatchGeneratorTest.java @@ -0,0 +1,40 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.generator; + +import com.intellij.psi.PsiFile; +import com.magento.idea.magento2plugin.actions.generation.ModuleSetupDataPatchData; +import com.magento.idea.magento2plugin.magento.files.ModuleSetupDataPatchFile; + +public final class ModuleSetupDataPatchGeneratorTest extends BaseGeneratorTestCase { + + private static final String CLASS_NAME = "TestClassPatch"; + + /** + * Test module README.md file generation. + */ + public void testGenerateModuleSetupDataPatchFile() { + final PsiFile expectedFile = myFixture.configureByFile( + getFixturePath(CLASS_NAME + ModuleSetupDataPatchFile.EXTENSION) + ); + final ModuleSetupDataPatchGenerator generator = new ModuleSetupDataPatchGenerator( + new ModuleSetupDataPatchData( + "Foo", + "Bar", + getProjectDirectory(), + CLASS_NAME + + ), + myFixture.getProject() + ); + final PsiFile generatedFile = generator.generate("test"); + + assertGeneratedFileIsCorrect( + expectedFile, + generatedFile + ); + } +}