├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── META-INF └── plugin.xml ├── README.md ├── idea-php-oxid.iml └── src ├── de └── espend │ └── idea │ └── oxid │ ├── OxidPluginIcons.java │ ├── OxidProjectComponent.java │ ├── completion │ ├── PhpCompletionProvider.java │ └── SmartyFileCompletionProvider.java │ ├── dict │ └── metadata │ │ ├── MetadataBlock.java │ │ └── MetadataSetting.java │ ├── icons │ ├── line_marker_oxid.png │ ├── line_marker_oxid@2.png │ ├── oxid.png │ └── oxid@2.png │ ├── inspection │ └── OxidMetadataFileInspection.java │ ├── navigation │ ├── PhpGoToHandler.java │ ├── SmartyGoToHandler.java │ └── SmartyTemplateLineMarkerProvider.java │ ├── stub │ └── OxidContentIdentIndexer.java │ ├── types │ └── OxidFactoryTypeProvider.java │ └── utils │ ├── MetadataUtil.java │ ├── ModuleUtil.java │ ├── OxidUtil.java │ ├── PhpMetadataUtil.java │ ├── SmartyBlockUtil.java │ ├── SmartyPattern.java │ ├── TemplateUtil.java │ └── TranslationUtil.java └── inspectionDescriptions └── OxidMetadataFile.html /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | /.idea 3 | /.idea/misc.xml 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5 2 | * Move OxidMetadataFileInspection into PsiElementVisitor 3 | * Migration to PhpTypeProvider3 for PhpStorm 2017.3 [#12](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/12) 4 | * Fix com.intellij.openapi.project.IndexNotReadyException in custom trait generator for class overwrites [#10](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/10) 5 | 6 | ## 0.4 7 | * Fix NullPointerException in visitMetadataKey [#5](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/5) 8 | * fix modules without vendor are not in .phpstorm-oxid.meta.php [#2](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/2) 9 | * Add cache for metadata getExtendsList, as this one is the slowest stuff 10 | 11 | ## 0.3.1 12 | * Support core "lang.php" for translations 13 | * Fix repeating File Cache Conflict since [#4](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/4) 14 | 15 | ## 0.3 16 | * Implement traits generator for extend metadata class, so every module class knows foreign inheritance 17 | * Add support oxmultilang::ident and oxcontent::ident in smarty 18 | 19 | ## 0.2.1 20 | * Fix lookup elements for factory methods where collected in nowhere 21 | 22 | ## 0.2 23 | * proxy getArrayKeyValueMap for npe fix [#1](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/1) 24 | * Recursive search for module or vendor module structure [#2](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/2) 25 | * Support array key of "extend" in metadata [#3](https://github.com/Haehnchen/idea-php-oxid-plugin/issues/3) 26 | * Some performance improvements 27 | 28 | ## 0.1 29 | * Initial release with dep on [Symfony2 Plugin](http://plugins.jetbrains.com/plugin/7219) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Daniel Espendiller 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | de.espend.idea.oxid 3 | OXID Plugin 4 | 0.5 5 | Daniel Espendiller 6 | 7 | OXID Plugin
9 |
10 | Issues | Donate 11 | 12 |
13 | Because of code sharing install Symfony Plugin and enable it per project 14 |

15 | 16 |

Dependencies

17 | 20 | 21 |

Features

22 | 35 | 36 | ]]>
37 | 38 | 40 |
  • Move OxidMetadataFileInspection into PsiElementVisitor
  • 41 |
  • Migration to PhpTypeProvider3 for PhpStorm 2017.3 #12
  • 42 |
  • Fix com.intellij.openapi.project.IndexNotReadyException in custom trait generator for class overwrites #10
  • 43 | 44 | 45 |
    46 | see full changelog | Donate 47 | ]]> 48 |
    49 | 50 | 51 | 52 | 53 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | com.jetbrains.php 84 | com.intellij.modules.platform 85 | fr.adrienbrault.idea.symfony2plugin 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | de.espend.idea.oxid.OxidProjectComponent 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
    -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OXID Plugin 2 | ========================== 3 | [![Version](http://phpstorm.espend.de/badge/7853/version)](https://plugins.jetbrains.com/plugin/7853) 4 | [![Downloads](http://phpstorm.espend.de/badge/7853/downloads)](https://plugins.jetbrains.com/plugin/7853) 5 | [![Downloads last month](http://phpstorm.espend.de/badge/7853/last-month)](https://plugins.jetbrains.com/plugin/7853) 6 | [![Donate to this project using Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/DanielEspendiller) 7 | 8 | IntelliJ IDEA / PhpStorm Plugin for [OXID](http://www.oxid-esales.com/ "OXID") 9 | 10 | Key | Value 11 | ----------- | ----------- 12 | Plugin Url | https://plugins.jetbrains.com/plugin/7853 13 | ID | de.espend.idea.oxid 14 | Changelog | [CHANGELOG](CHANGELOG.md) 15 | 16 | Install 17 | --------------------- 18 | * Install the plugin by going to `Settings -> Plugins -> Browse repositories` and then search for `OXID Plugin`. 19 | * Enabled [Symfony Plugin](https://plugins.jetbrains.com/plugin/7219) per project (File > Settings > Symfony Plugin ) 20 | 21 | Screenshots 22 | --------------------- 23 | 24 | ![PHP](https://plugins.jetbrains.com/files/7853/screenshot_15125.png) 25 | ![Metadata](https://plugins.jetbrains.com/files/7853/screenshot_15124.png) 26 | ![Smarty](https://plugins.jetbrains.com/files/7853/screenshot_15126.png) -------------------------------------------------------------------------------- /idea-php-oxid.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/OxidPluginIcons.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | 5 | import javax.swing.*; 6 | 7 | /** 8 | * @author Daniel Espendiller 9 | */ 10 | public class OxidPluginIcons { 11 | public static final Icon OXID = IconLoader.getIcon("icons/oxid.png"); 12 | public static final Icon OXID_LINEMARKER = IconLoader.getIcon("icons/line_marker_oxid.png"); 13 | } 14 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/OxidProjectComponent.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid; 2 | 3 | import com.intellij.openapi.components.ProjectComponent; 4 | import com.intellij.openapi.progress.ProgressIndicator; 5 | import com.intellij.openapi.progress.Task; 6 | import com.intellij.openapi.project.DumbService; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.psi.PsiElement; 9 | import de.espend.idea.oxid.utils.OxidUtil; 10 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.Timer; 15 | import java.util.TimerTask; 16 | 17 | /** 18 | * @author Daniel Espendiller 19 | */ 20 | public class OxidProjectComponent implements ProjectComponent { 21 | 22 | public static final int DUMPER_PERIODE = 600 * 1000; 23 | 24 | private final Project project; 25 | private Timer timer; 26 | 27 | public OxidProjectComponent(Project project) { 28 | this.project = project; 29 | } 30 | 31 | @Override 32 | public void projectOpened() { 33 | 34 | DumbService.getInstance(this.project).smartInvokeLater(() -> { 35 | if(PhpElementsUtil.getClassInterface(project, "\\oxArticle") == null) { 36 | return; 37 | } 38 | 39 | new Task.Backgroundable(project, "OXID extends dumper", true) { 40 | @Override 41 | public void run(@NotNull ProgressIndicator indicator) { 42 | if(DumbService.getInstance(project).isDumb()) { 43 | return; 44 | } 45 | 46 | OxidUtil.buildClassMetadataFile(project); 47 | } 48 | }.queue(); 49 | 50 | 51 | timer = new Timer(); 52 | 53 | timer.schedule(new TimerTask() { 54 | public void run() { 55 | 56 | if(DumbService.getInstance(project).isDumb()) { 57 | return; 58 | } 59 | 60 | OxidUtil.buildClassMetadataFile(project); 61 | } 62 | }, DUMPER_PERIODE, DUMPER_PERIODE); 63 | 64 | }); 65 | 66 | 67 | } 68 | 69 | @Override 70 | public void projectClosed() { 71 | if(this.timer != null) { 72 | this.timer.cancel(); 73 | timer.purge(); 74 | timer = null; 75 | } 76 | } 77 | 78 | @Override 79 | public void initComponent() { 80 | 81 | } 82 | 83 | @Override 84 | public void disposeComponent() { 85 | 86 | } 87 | 88 | @NotNull 89 | @Override 90 | public String getComponentName() { 91 | return "OXID Plugin"; 92 | } 93 | 94 | public static boolean isValidForProject(@Nullable PsiElement psiElement) { 95 | if(psiElement == null) { 96 | return false; 97 | } 98 | 99 | // @TODO: plugin switch 100 | // VfsUtil.findRelativeFile(psiElement.getProject().getBaseDir(), "engine", "Shopware", "Kernel.php") != null 101 | // PhpElementsUtil.getClassInterface(psiElement.getProject(), "\\Enlight_Controller_Action") != null 102 | 103 | return true; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/completion/PhpCompletionProvider.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.completion; 2 | 3 | import com.intellij.codeInsight.completion.*; 4 | import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; 5 | import com.intellij.codeInsight.lookup.LookupElement; 6 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 7 | import com.intellij.openapi.fileTypes.FileType; 8 | import com.intellij.openapi.vfs.VfsUtil; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.intellij.openapi.vfs.VirtualFileVisitor; 11 | import com.intellij.patterns.PlatformPatterns; 12 | import com.intellij.psi.PsiDirectory; 13 | import com.intellij.psi.PsiElement; 14 | import com.intellij.psi.PsiFile; 15 | import com.intellij.psi.PsiManager; 16 | import com.intellij.psi.search.FilenameIndex; 17 | import com.intellij.psi.search.GlobalSearchScope; 18 | import com.intellij.psi.util.PsiTreeUtil; 19 | import com.intellij.util.ProcessingContext; 20 | import com.jetbrains.php.PhpIndex; 21 | import com.jetbrains.php.completion.PhpClassLookupElement; 22 | import com.jetbrains.php.lang.PhpFileType; 23 | import com.jetbrains.php.lang.parser.PhpElementTypes; 24 | import com.jetbrains.php.lang.psi.PhpFile; 25 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 26 | import com.jetbrains.php.lang.psi.elements.*; 27 | import com.jetbrains.smarty.SmartyFileType; 28 | import de.espend.idea.oxid.OxidPluginIcons; 29 | import de.espend.idea.oxid.OxidProjectComponent; 30 | import de.espend.idea.oxid.dict.metadata.MetadataSetting; 31 | import de.espend.idea.oxid.utils.*; 32 | import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; 33 | import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; 34 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; 35 | import fr.adrienbrault.idea.symfony2plugin.util.completion.PhpClassReferenceInsertHandler; 36 | import org.apache.commons.lang.StringUtils; 37 | import org.jetbrains.annotations.NotNull; 38 | 39 | import java.util.*; 40 | 41 | /** 42 | * @author Daniel Espendiller 43 | */ 44 | public class PhpCompletionProvider extends CompletionContributor { 45 | 46 | public PhpCompletionProvider() { 47 | 48 | // ['extend' => ['key' => ''] ] 49 | extend( 50 | CompletionType.BASIC, MetadataUtil.getMetadataFilePattern(), 51 | new CompletionProvider() { 52 | @Override 53 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 54 | 55 | PsiElement originalPosition = parameters.getOriginalPosition(); 56 | if(originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 57 | return; 58 | } 59 | 60 | PsiElement parent = originalPosition.getParent(); 61 | if(!(parent instanceof StringLiteralExpression) || !PhpMetadataUtil.isModuleKeyInFlatArray((StringLiteralExpression) parent, "extend")) { 62 | return; 63 | } 64 | 65 | ModuleUtil.visitModuleFile(originalPosition.getContainingFile(), new ModuleUtil.ModuleFileVisitor() { 66 | @Override 67 | public void visit(@NotNull VirtualFile virtualFile, @NotNull String relativePath) { 68 | 69 | if (virtualFile.getFileType() != PhpFileType.INSTANCE || !relativePath.endsWith(".php")) { 70 | return; 71 | } 72 | 73 | result.addElement(LookupElementBuilder.create(relativePath.substring(0, relativePath.length() - 4)).withIcon(virtualFile.getFileType().getIcon())); 74 | } 75 | }); 76 | 77 | } 78 | } 79 | ); 80 | 81 | 82 | // ['extend' => ['file' => ''] ] 83 | extend( 84 | CompletionType.BASIC, MetadataUtil.getMetadataFilePattern(), 85 | new CompletionProvider() { 86 | @Override 87 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 88 | 89 | PsiElement originalPosition = parameters.getOriginalPosition(); 90 | if(originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 91 | return; 92 | } 93 | 94 | PsiElement parent = originalPosition.getParent(); 95 | if(!(parent instanceof StringLiteralExpression) || !PhpMetadataUtil.isInTemplateWithKey((StringLiteralExpression) parent, "file")) { 96 | return; 97 | } 98 | 99 | ModuleUtil.visitModuleTemplatesInMetadataScope(originalPosition.getContainingFile(), new ModuleUtil.ModuleFileVisitor() { 100 | @Override 101 | public void visit(@NotNull VirtualFile virtualFile, @NotNull String relativePath) { 102 | result.addElement(LookupElementBuilder.create(relativePath).withIcon(virtualFile.getFileType().getIcon())); 103 | } 104 | }); 105 | 106 | } 107 | } 108 | ); 109 | 110 | // ['files' => [...] ] 111 | extend( 112 | CompletionType.BASIC, MetadataUtil.getMetadataFilePattern(), 113 | new ModuleFileParametersCompletionProvider("files", PhpFileType.INSTANCE) 114 | ); 115 | 116 | // ['templates' => [...] ] 117 | extend( 118 | CompletionType.BASIC, MetadataUtil.getMetadataFilePattern(), 119 | new ModuleFileParametersCompletionProvider("templates", SmartyFileType.INSTANCE) 120 | ); 121 | 122 | // "blocks" => [{"template" => 'foo'}] 123 | extend( 124 | CompletionType.BASIC, MetadataUtil.getMetadataFilePattern(), 125 | new CompletionProvider() { 126 | @Override 127 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 128 | 129 | PsiElement originalPosition = parameters.getOriginalPosition(); 130 | if(originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 131 | return; 132 | } 133 | 134 | PsiElement parent = originalPosition.getParent(); 135 | if(!(parent instanceof StringLiteralExpression) || !PhpMetadataUtil.isInTemplateWithKey((StringLiteralExpression) parent, "template")) { 136 | return; 137 | } 138 | 139 | TemplateUtil.collectFiles(parameters.getPosition().getProject(), new TemplateUtil.SmartyTemplateVisitor() { 140 | @Override 141 | public void visitFile(VirtualFile virtualFile, String fileName) { 142 | result.addElement(LookupElementBuilder.create(fileName).withIcon(virtualFile.getFileType().getIcon())); 143 | } 144 | }); 145 | 146 | } 147 | } 148 | ); 149 | 150 | // "blocks" => [{"block" => 'foo'}] 151 | extend( 152 | CompletionType.BASIC, MetadataUtil.getMetadataFilePattern(), 153 | new CompletionProvider() { 154 | @Override 155 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 156 | 157 | PsiElement originalPosition = parameters.getOriginalPosition(); 158 | if(originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 159 | return; 160 | } 161 | 162 | PsiElement parent = originalPosition.getParent(); 163 | if(!(parent instanceof StringLiteralExpression) || !PhpMetadataUtil.isInTemplateWithKey((StringLiteralExpression) parent, "block")) { 164 | return; 165 | } 166 | 167 | ArrayCreationExpression arrayCreation = PsiTreeUtil.getParentOfType(originalPosition, ArrayCreationExpression.class); 168 | if(arrayCreation == null) { 169 | return; 170 | } 171 | 172 | String template = PhpElementsUtil.getArrayValueString(arrayCreation, "template"); 173 | if(template == null) { 174 | return; 175 | } 176 | 177 | result.addAllElements(TemplateUtil.getBlockFileLookupElements(parameters.getPosition().getProject(), template)); 178 | } 179 | } 180 | ); 181 | 182 | // \oxConfig::getConfigParam() 183 | // \oxConfig::setConfigParam() 184 | extend( 185 | CompletionType.BASIC, PlatformPatterns.psiElement(), 186 | new CompletionProvider() { 187 | @Override 188 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 189 | 190 | PsiElement originalPosition = parameters.getOriginalPosition(); 191 | if(originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 192 | return; 193 | } 194 | 195 | if(new MethodMatcher.StringParameterRecursiveMatcher(originalPosition.getContext(), 0) 196 | .withSignature("\\oxConfig", "getConfigParam") 197 | .withSignature("\\oxConfig", "getConfigParam") 198 | .match() == null) { 199 | 200 | return; 201 | } 202 | 203 | Set settings = new HashSet(); 204 | 205 | for (PsiFile psiFile : FilenameIndex.getFilesByName(originalPosition.getProject(), "metadata.php", GlobalSearchScope.allScope(originalPosition.getProject()))) { 206 | for (MetadataSetting setting : MetadataUtil.getSettings(psiFile)) { 207 | settings.add(setting.getName()); 208 | } 209 | } 210 | 211 | for (String setting : settings) { 212 | result.addElement(LookupElementBuilder.create(setting).withIcon(OxidPluginIcons.OXID)); 213 | } 214 | } 215 | } 216 | ); 217 | 218 | // oxRegistry::get, oxNew 219 | extend( 220 | CompletionType.BASIC, PlatformPatterns.psiElement(), 221 | new CompletionProvider() { 222 | @Override 223 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 224 | 225 | PsiElement originalPosition = parameters.getOriginalPosition(); 226 | if (originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 227 | return; 228 | } 229 | 230 | PsiElement parent = originalPosition.getContext(); 231 | if(!(parent instanceof StringLiteralExpression)) { 232 | return; 233 | } 234 | 235 | if(!OxidUtil.isFactory((StringLiteralExpression) parent)) { 236 | return; 237 | } 238 | 239 | String contents = ((StringLiteralExpression) parent).getContents(); 240 | result.addAllElements(OxidUtil.getOverloadAbleClasses(parent.getProject(), contents)); 241 | } 242 | } 243 | ); 244 | 245 | // \oxLang::translateString() 246 | extend( 247 | CompletionType.BASIC, PlatformPatterns.psiElement(), 248 | new CompletionProvider() { 249 | @Override 250 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 251 | 252 | PsiElement originalPosition = parameters.getOriginalPosition(); 253 | if (originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 254 | return; 255 | } 256 | 257 | if (new MethodMatcher.StringParameterRecursiveMatcher(originalPosition.getContext(), 0) 258 | .withSignature("\\oxLang", "translateString") 259 | .match() == null) { 260 | 261 | return; 262 | } 263 | 264 | result.addAllElements(TranslationUtil.getTranslationLookupElements(originalPosition.getProject())); 265 | } 266 | } 267 | ); 268 | 269 | 270 | // ['extend' => ['key'] ] 271 | extend( 272 | CompletionType.BASIC, MetadataUtil.getMetadataFilePattern(), 273 | new CompletionProvider() { 274 | @Override 275 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 276 | 277 | PsiElement originalPosition = parameters.getOriginalPosition(); 278 | if (originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 279 | return; 280 | } 281 | 282 | PsiElement parent = originalPosition.getParent(); 283 | if(!(parent instanceof StringLiteralExpression) || !PhpMetadataUtil.isExtendKey((StringLiteralExpression) parent)) { 284 | return; 285 | } 286 | 287 | result.addAllElements(OxidUtil.getOverloadAbleClasses(parent.getProject(), ((StringLiteralExpression) parent).getContents())); 288 | } 289 | } 290 | ); 291 | 292 | } 293 | 294 | private static class ModuleFileParametersCompletionProvider extends CompletionProvider { 295 | 296 | @NotNull 297 | private final String key; 298 | 299 | @NotNull 300 | private final FileType fileType; 301 | 302 | public ModuleFileParametersCompletionProvider(@NotNull String key, @NotNull FileType fileType) { 303 | this.key = key; 304 | this.fileType = fileType; 305 | } 306 | 307 | @Override 308 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 309 | 310 | PsiElement originalPosition = parameters.getOriginalPosition(); 311 | if(originalPosition == null || !OxidProjectComponent.isValidForProject(originalPosition)) { 312 | return; 313 | } 314 | 315 | PsiElement parent = originalPosition.getParent(); 316 | if(!(parent instanceof StringLiteralExpression) || !PhpMetadataUtil.isModuleKeyInFlatArray((StringLiteralExpression) parent, key)) { 317 | return; 318 | } 319 | 320 | ModuleUtil.visitModuleFile(originalPosition.getContainingFile(), new ModuleUtil.ModuleFileVisitor() { 321 | @Override 322 | public void visit(@NotNull VirtualFile virtualFile, @NotNull String relativePath) { 323 | 324 | if (virtualFile.getFileType() != fileType) { 325 | return; 326 | } 327 | 328 | result.addElement(LookupElementBuilder.create(relativePath).withIcon(virtualFile.getFileType().getIcon())); 329 | } 330 | }); 331 | 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/completion/SmartyFileCompletionProvider.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.completion; 2 | 3 | import com.intellij.codeInsight.completion.*; 4 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.intellij.psi.PsiElement; 7 | import com.intellij.util.ProcessingContext; 8 | import de.espend.idea.oxid.OxidPluginIcons; 9 | import de.espend.idea.oxid.OxidProjectComponent; 10 | import de.espend.idea.oxid.utils.SmartyPattern; 11 | import de.espend.idea.oxid.utils.TemplateUtil; 12 | import de.espend.idea.oxid.utils.TranslationUtil; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.Set; 16 | 17 | /** 18 | * @author Daniel Espendiller 19 | */ 20 | public class SmartyFileCompletionProvider extends CompletionContributor { 21 | 22 | public SmartyFileCompletionProvider() { 23 | 24 | extend( 25 | CompletionType.BASIC, SmartyPattern.getFilePattern(), 26 | new CompletionProvider() { 27 | @Override 28 | protected void addCompletions(final @NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 29 | 30 | if(!OxidProjectComponent.isValidForProject(parameters.getOriginalPosition())) { 31 | return; 32 | } 33 | 34 | attachTemplateFiles(parameters, result); 35 | } 36 | } 37 | ); 38 | 39 | extend( 40 | CompletionType.BASIC, SmartyPattern.getBlockPattern(), 41 | new CompletionProvider() { 42 | @Override 43 | protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 44 | 45 | PsiElement originalPosition = parameters.getOriginalPosition(); 46 | if(!OxidProjectComponent.isValidForProject(originalPosition)) { 47 | return; 48 | } 49 | 50 | Set templateNames = TemplateUtil.getTemplateNames(originalPosition.getProject(), originalPosition.getContainingFile().getVirtualFile()); 51 | if(templateNames.size() == 0) { 52 | return; 53 | } 54 | 55 | result.addAllElements(TemplateUtil.getBlockFileLookupElements(parameters.getPosition().getProject(), templateNames.toArray(new String[templateNames.size()]))); 56 | } 57 | 58 | } 59 | 60 | ); 61 | 62 | extend( 63 | CompletionType.BASIC, SmartyPattern.getAttributeInsideTagPattern("ident", "oxmultilang"), 64 | new CompletionProvider() { 65 | @Override 66 | protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 67 | 68 | PsiElement originalPosition = parameters.getOriginalPosition(); 69 | if(!OxidProjectComponent.isValidForProject(originalPosition)) { 70 | return; 71 | } 72 | 73 | result.addAllElements(TranslationUtil.getTranslationLookupElements(originalPosition.getProject())); 74 | } 75 | 76 | } 77 | 78 | ); 79 | 80 | extend( 81 | CompletionType.BASIC, SmartyPattern.getAttributeInsideTagPattern("ident", "oxcontent"), 82 | new CompletionProvider() { 83 | @Override 84 | protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, final @NotNull CompletionResultSet result) { 85 | 86 | PsiElement originalPosition = parameters.getOriginalPosition(); 87 | if(!OxidProjectComponent.isValidForProject(originalPosition)) { 88 | return; 89 | } 90 | 91 | for (String s : TemplateUtil.getContentIdents(originalPosition.getProject())) { 92 | result.addElement(LookupElementBuilder.create(s).withIcon(OxidPluginIcons.OXID)); 93 | } 94 | 95 | } 96 | 97 | } 98 | 99 | ); 100 | 101 | } 102 | 103 | private void attachTemplateFiles(CompletionParameters parameters, final CompletionResultSet result) { 104 | TemplateUtil.collectFiles(parameters.getPosition().getProject(), new TemplateUtil.SmartyTemplateVisitor() { 105 | @Override 106 | public void visitFile(VirtualFile virtualFile, String fileName) { 107 | result.addElement(LookupElementBuilder.create(fileName).withIcon(virtualFile.getFileType().getIcon())); 108 | } 109 | }); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/dict/metadata/MetadataBlock.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.dict.metadata; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * @author Daniel Espendiller 10 | */ 11 | public class MetadataBlock { 12 | 13 | @Nullable 14 | private final String template; 15 | 16 | @Nullable 17 | private final String block; 18 | 19 | @Nullable 20 | private final String file; 21 | 22 | public MetadataBlock(@Nullable String template, @Nullable String block, @Nullable String file) { 23 | this.template = template; 24 | this.block = block; 25 | this.file = file; 26 | } 27 | 28 | @Nullable 29 | public String getTemplate() { 30 | return template; 31 | } 32 | 33 | @Nullable 34 | public String getBlock() { 35 | return block; 36 | } 37 | 38 | @Nullable 39 | public String getFile() { 40 | return file; 41 | } 42 | 43 | @Nullable 44 | public static MetadataBlock create(@NotNull Map map) { 45 | 46 | if(!map.containsKey("template") && !map.containsKey("block") && !map.containsKey("file")) { 47 | return null; 48 | } 49 | 50 | return new MetadataBlock( 51 | map.get("template"), 52 | map.get("block"), 53 | map.get("file") 54 | ); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/dict/metadata/MetadataSetting.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.dict.metadata; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @author Daniel Espendiller 11 | */ 12 | public class MetadataSetting { 13 | 14 | @NotNull 15 | private final PsiElement source; 16 | @Nullable 17 | private final String group; 18 | 19 | @Nullable 20 | private final String name; 21 | 22 | @Nullable 23 | private final String type; 24 | 25 | private MetadataSetting(@NotNull PsiElement source, @Nullable String group, @Nullable String name, @Nullable String type) { 26 | this.source = source; 27 | this.group = group; 28 | this.name = name; 29 | this.type = type; 30 | } 31 | 32 | @Nullable 33 | public String getGroup() { 34 | return group; 35 | } 36 | 37 | @Nullable 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | @Nullable 43 | public String getType() { 44 | return type; 45 | } 46 | 47 | @NotNull 48 | public PsiElement getSource() { 49 | return source; 50 | } 51 | 52 | @Nullable 53 | public static MetadataSetting create(@NotNull PsiElement source, @NotNull Map map) { 54 | 55 | if(!map.containsKey("group") && !map.containsKey("name") && !map.containsKey("type")) { 56 | return null; 57 | } 58 | 59 | return new MetadataSetting( 60 | source, 61 | map.get("group"), 62 | map.get("name"), 63 | map.get("type") 64 | ); 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/icons/line_marker_oxid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haehnchen/idea-php-oxid-plugin/edb86e6b41e422957cc86ea78293f7427ff1e764/src/de/espend/idea/oxid/icons/line_marker_oxid.png -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/icons/line_marker_oxid@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haehnchen/idea-php-oxid-plugin/edb86e6b41e422957cc86ea78293f7427ff1e764/src/de/espend/idea/oxid/icons/line_marker_oxid@2.png -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/icons/oxid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haehnchen/idea-php-oxid-plugin/edb86e6b41e422957cc86ea78293f7427ff1e764/src/de/espend/idea/oxid/icons/oxid.png -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/icons/oxid@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haehnchen/idea-php-oxid-plugin/edb86e6b41e422957cc86ea78293f7427ff1e764/src/de/espend/idea/oxid/icons/oxid@2.png -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/inspection/OxidMetadataFileInspection.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.inspection; 2 | 3 | import com.intellij.codeInspection.LocalInspectionTool; 4 | import com.intellij.codeInspection.ProblemHighlightType; 5 | import com.intellij.codeInspection.ProblemsHolder; 6 | import com.intellij.psi.PsiElement; 7 | import com.intellij.psi.PsiElementVisitor; 8 | import com.intellij.psi.PsiFile; 9 | import com.intellij.psi.PsiRecursiveElementWalkingVisitor; 10 | import com.jetbrains.php.lang.PhpFileType; 11 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; 12 | import com.jetbrains.smarty.SmartyFileType; 13 | import de.espend.idea.oxid.navigation.PhpGoToHandler; 14 | import de.espend.idea.oxid.utils.PhpMetadataUtil; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | 20 | /** 21 | * @author Daniel Espendiller 22 | */ 23 | public class OxidMetadataFileInspection extends LocalInspectionTool { 24 | 25 | @NotNull 26 | @Override 27 | public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, boolean isOnTheFly) { 28 | 29 | PsiFile psiFile = holder.getFile(); 30 | 31 | String name = psiFile.getName(); 32 | if(!name.toLowerCase().contains("metadata.php")) { 33 | return super.buildVisitor(holder, isOnTheFly); 34 | } 35 | 36 | return new MyPsiRecursiveElementWalkingVisitor(holder); 37 | } 38 | 39 | private static class MyPsiRecursiveElementWalkingVisitor extends PsiElementVisitor { 40 | @NotNull 41 | private final ProblemsHolder holder; 42 | 43 | private MyPsiRecursiveElementWalkingVisitor(@NotNull ProblemsHolder holder) { 44 | this.holder = holder; 45 | } 46 | 47 | @Override 48 | public void visitElement(PsiElement element) { 49 | 50 | if(element instanceof StringLiteralExpression) { 51 | visitLiteralExpression((StringLiteralExpression) element); 52 | } 53 | 54 | super.visitElement(element); 55 | } 56 | 57 | private void visitLiteralExpression(StringLiteralExpression element) { 58 | 59 | // @TODO: refactor in providers 60 | if(PhpMetadataUtil.isModuleKeyInFlatArray(element, "extend")) { 61 | Collection psiElements = new ArrayList(); 62 | PhpGoToHandler.attachMetadataExtends(element, psiElements); 63 | if(psiElements.size() == 0) { 64 | holder.registerProblem(element, "File not found", ProblemHighlightType.GENERIC_ERROR_OR_WARNING); 65 | } 66 | } else if(PhpMetadataUtil.isModuleKeyInFlatArray(element, "files")) { 67 | Collection psiElements = new ArrayList(); 68 | PhpGoToHandler.attachAllFileTypes(element, psiElements, PhpFileType.INSTANCE); 69 | if(psiElements.size() == 0) { 70 | holder.registerProblem(element, "File not found", ProblemHighlightType.GENERIC_ERROR_OR_WARNING); 71 | } 72 | } else if(PhpMetadataUtil.isModuleKeyInFlatArray(element, "templates")) { 73 | Collection psiElements = new ArrayList(); 74 | PhpGoToHandler.attachAllFileTypes(element, psiElements, SmartyFileType.INSTANCE); 75 | if(psiElements.size() == 0) { 76 | holder.registerProblem(element, "File not found", ProblemHighlightType.GENERIC_ERROR_OR_WARNING); 77 | } 78 | } else if(PhpMetadataUtil.isInTemplateWithKey(element, "file")) { 79 | Collection psiElements = new ArrayList(); 80 | PhpGoToHandler.attachTemplateFileTypes(element, psiElements); 81 | if(psiElements.size() == 0) { 82 | holder.registerProblem(element, "File not found", ProblemHighlightType.GENERIC_ERROR_OR_WARNING); 83 | } 84 | } 85 | 86 | } 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/navigation/PhpGoToHandler.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.navigation; 2 | 3 | import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler; 4 | import com.intellij.openapi.actionSystem.DataContext; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.fileTypes.FileType; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.psi.PsiManager; 11 | import com.intellij.psi.search.FilenameIndex; 12 | import com.intellij.psi.search.GlobalSearchScope; 13 | import com.intellij.psi.util.PsiTreeUtil; 14 | import com.jetbrains.php.lang.PhpFileType; 15 | import com.jetbrains.php.lang.psi.elements.*; 16 | import com.jetbrains.smarty.SmartyFileType; 17 | import de.espend.idea.oxid.OxidProjectComponent; 18 | import de.espend.idea.oxid.dict.metadata.MetadataSetting; 19 | import de.espend.idea.oxid.utils.*; 20 | import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; 21 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; 22 | import org.apache.commons.lang.StringUtils; 23 | import org.jetbrains.annotations.NotNull; 24 | import org.jetbrains.annotations.Nullable; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Collection; 28 | 29 | /** 30 | * @author Daniel Espendiller 31 | */ 32 | public class PhpGoToHandler implements GotoDeclarationHandler { 33 | 34 | @Nullable 35 | @Override 36 | public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Editor editor) { 37 | 38 | if(!OxidProjectComponent.isValidForProject(psiElement)) { 39 | return new PsiElement[0]; 40 | } 41 | 42 | Collection psiElements = new ArrayList(); 43 | 44 | PsiElement parent = psiElement.getParent(); 45 | if(parent instanceof StringLiteralExpression) { 46 | 47 | // in metadata.php 48 | if(MetadataUtil.getMetadataFilePattern().accepts(parent)) { 49 | 50 | // ['extend' => ['key' => ''] ] 51 | if(PhpMetadataUtil.isModuleKeyInFlatArray((StringLiteralExpression) parent, "extend")) { 52 | attachMetadataExtends((StringLiteralExpression) parent, psiElements); 53 | } 54 | 55 | // ['files' => [...] ] 56 | if(PhpMetadataUtil.isModuleKeyInFlatArray((StringLiteralExpression) parent, "files")) { 57 | attachAllFileTypes((StringLiteralExpression) parent, psiElements, PhpFileType.INSTANCE); 58 | } 59 | 60 | // ['templates' => [...] ] 61 | if(PhpMetadataUtil.isModuleKeyInFlatArray((StringLiteralExpression) parent, "templates")) { 62 | attachAllFileTypes((StringLiteralExpression) parent, psiElements, SmartyFileType.INSTANCE); 63 | } 64 | 65 | // ['extend' => ['file' => ''] ] 66 | if(PhpMetadataUtil.isInTemplateWithKey((StringLiteralExpression) parent, "file")) { 67 | attachTemplateFileTypes((StringLiteralExpression) parent, psiElements); 68 | } 69 | 70 | // "blocks" => [{"template" => 'foo'}] 71 | if(PhpMetadataUtil.isInTemplateWithKey((StringLiteralExpression) parent, "template")) { 72 | attachTemplateFile((StringLiteralExpression) parent, psiElements); 73 | } 74 | 75 | // "blocks" => [{"block" => 'foo'}] 76 | if(PhpMetadataUtil.isInTemplateWithKey((StringLiteralExpression) parent, "block")) { 77 | attachTemplateBlocks((StringLiteralExpression) parent, psiElements); 78 | } 79 | 80 | // ['extend' => ['key'] ] 81 | if (PhpMetadataUtil.isExtendKey((StringLiteralExpression) parent)) { 82 | attachFactoryClasses((StringLiteralExpression) parent, psiElements); 83 | } 84 | 85 | } 86 | 87 | // \oxConfig::getConfigParam() 88 | // \oxConfig::setConfigParam() 89 | if(new MethodMatcher.StringParameterRecursiveMatcher(parent, 0) 90 | .withSignature("\\oxConfig", "getConfigParam") 91 | .withSignature("\\oxConfig", "getConfigParam").match() != null) { 92 | 93 | attachConfigs((StringLiteralExpression) parent, psiElements); 94 | } 95 | 96 | // \oxLang::translateString() 97 | if (new MethodMatcher.StringParameterRecursiveMatcher(parent, 0) 98 | .withSignature("\\oxLang", "translateString") 99 | .match() != null) { 100 | 101 | attachTranslations((StringLiteralExpression) parent, psiElements); 102 | } 103 | 104 | // oxNew(); 105 | // oxRegistry::get(); 106 | if (OxidUtil.isFactory((StringLiteralExpression) parent)) { 107 | attachFactoryClasses((StringLiteralExpression) parent, psiElements); 108 | } 109 | 110 | } 111 | 112 | return psiElements.toArray(new PsiElement[psiElements.size()]); 113 | } 114 | 115 | private void attachFactoryClasses(StringLiteralExpression parent, Collection psiElements) { 116 | 117 | String contents = parent.getContents(); 118 | if(StringUtils.isBlank(contents)) { 119 | return; 120 | } 121 | 122 | PhpClass classInterface = PhpElementsUtil.getClassInterface(parent.getProject(), contents); 123 | if(classInterface == null) { 124 | return; 125 | } 126 | 127 | psiElements.add(classInterface); 128 | } 129 | 130 | private void attachTranslations(@NotNull StringLiteralExpression parent, @NotNull Collection psiElements) { 131 | 132 | String contents = parent.getContents(); 133 | if(StringUtils.isBlank(contents)) { 134 | return; 135 | } 136 | 137 | psiElements.addAll(TranslationUtil.getTranslationTargets(parent.getProject(), contents)); 138 | } 139 | 140 | private void attachConfigs(@NotNull StringLiteralExpression parent, @NotNull Collection psiElements) { 141 | 142 | String contents = parent.getContents(); 143 | if(StringUtils.isBlank(contents)) { 144 | return; 145 | } 146 | 147 | for (PsiFile psiFile : FilenameIndex.getFilesByName(parent.getProject(), "metadata.php", GlobalSearchScope.allScope(parent.getProject()))) { 148 | for (MetadataSetting setting : MetadataUtil.getSettings(psiFile)) { 149 | if (contents.equals(setting.getName())) { 150 | psiElements.add(setting.getSource()); 151 | } 152 | } 153 | } 154 | } 155 | 156 | private void attachTemplateBlocks(@NotNull final StringLiteralExpression psiElement, @NotNull final Collection psiElements) { 157 | 158 | final String contents = getPathFormattedString(psiElement); 159 | if (contents == null) { 160 | return; 161 | } 162 | 163 | 164 | ArrayCreationExpression arrayCreation = PsiTreeUtil.getParentOfType(psiElement, ArrayCreationExpression.class); 165 | if(arrayCreation == null) { 166 | return; 167 | } 168 | 169 | String template = PhpElementsUtil.getArrayValueString(arrayCreation, "template"); 170 | if(template == null) { 171 | return; 172 | } 173 | 174 | for (SmartyBlockUtil.SmartyBlock block : TemplateUtil.getBlocksTemplateName(psiElement.getProject(), template)) { 175 | if(block.getName().equals(contents)) { 176 | psiElements.add(block.getElement()); 177 | } 178 | } 179 | 180 | } 181 | 182 | private void attachTemplateFile(@NotNull final StringLiteralExpression psiElement, @NotNull final Collection psiElements) { 183 | 184 | final String contents = getPathFormattedString(psiElement); 185 | if (contents == null) { 186 | return; 187 | } 188 | 189 | TemplateUtil.collectFiles(psiElement.getProject(), new TemplateUtil.SmartyTemplateVisitor() { 190 | @Override 191 | public void visitFile(VirtualFile virtualFile, String fileName) { 192 | if(contents.equalsIgnoreCase(fileName)) { 193 | 194 | PsiFile file = PsiManager.getInstance(psiElement.getProject()).findFile(virtualFile); 195 | if(file != null) { 196 | psiElements.add(file); 197 | } 198 | 199 | } 200 | } 201 | }); 202 | 203 | } 204 | 205 | public static void attachTemplateFileTypes(StringLiteralExpression psiElement, Collection psiElements) { 206 | 207 | final String contents = getPathFormattedString(psiElement); 208 | if (contents == null) { 209 | return; 210 | } 211 | 212 | ModuleUtil.visitModuleTemplatesInMetadataScope(psiElement.getContainingFile(), new ContentEqualModuleFileVisitor(SmartyFileType.INSTANCE, contents, psiElement, psiElements)); 213 | } 214 | 215 | public static void attachMetadataExtends(@NotNull final StringLiteralExpression psiElement, final @NotNull Collection psiElements) { 216 | 217 | final String contents = getPathFormattedString(psiElement); 218 | if (contents == null) { 219 | return; 220 | } 221 | 222 | ModuleUtil.visitModuleFile(psiElement.getContainingFile(), new ModuleUtil.ModuleFileVisitor() { 223 | @Override 224 | public void visit(@NotNull VirtualFile virtualFile, @NotNull String relativePath) { 225 | 226 | if (virtualFile.getFileType() != PhpFileType.INSTANCE || !relativePath.toLowerCase().endsWith(".php") || !contents.equalsIgnoreCase(relativePath.substring(0, relativePath.length() - 4))) { 227 | return; 228 | } 229 | 230 | PsiFile file = PsiManager.getInstance(psiElement.getProject()).findFile(virtualFile); 231 | if(file != null) { 232 | psiElements.add(file); 233 | } 234 | 235 | } 236 | }); 237 | 238 | } 239 | 240 | public static void attachAllFileTypes(@NotNull final StringLiteralExpression psiElement, final @NotNull Collection psiElements, final FileType fileType) { 241 | 242 | final String contents = getPathFormattedString(psiElement); 243 | if (contents == null) { 244 | return; 245 | } 246 | 247 | ModuleUtil.visitModuleFile(psiElement.getContainingFile(), new ContentEqualModuleFileVisitor(fileType, contents, psiElement, psiElements)); 248 | } 249 | 250 | @Nullable 251 | private static String getPathFormattedString(@NotNull StringLiteralExpression psiElement) { 252 | String contents = psiElement.getContents(); 253 | if(StringUtils.isBlank(contents)) { 254 | return null; 255 | } 256 | 257 | if(contents.startsWith("/")) { 258 | contents = contents.substring(1); 259 | } 260 | 261 | return contents; 262 | } 263 | 264 | @Nullable 265 | @Override 266 | public String getActionText(DataContext dataContext) { 267 | return null; 268 | } 269 | 270 | private static class ContentEqualModuleFileVisitor implements ModuleUtil.ModuleFileVisitor { 271 | private final FileType fileType; 272 | private final String contents; 273 | private final StringLiteralExpression psiElement; 274 | private final Collection psiElements; 275 | 276 | public ContentEqualModuleFileVisitor(FileType fileType, String contents, StringLiteralExpression psiElement, Collection psiElements) { 277 | this.fileType = fileType; 278 | this.contents = contents; 279 | this.psiElement = psiElement; 280 | this.psiElements = psiElements; 281 | } 282 | 283 | @Override 284 | public void visit(@NotNull VirtualFile virtualFile, @NotNull String relativePath) { 285 | 286 | if (virtualFile.getFileType() != fileType || !contents.equalsIgnoreCase(relativePath)) { 287 | return; 288 | } 289 | 290 | PsiFile file = PsiManager.getInstance(psiElement.getProject()).findFile(virtualFile); 291 | if(file != null) { 292 | psiElements.add(file); 293 | } 294 | 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/navigation/SmartyGoToHandler.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.navigation; 2 | 3 | import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler; 4 | import com.intellij.openapi.actionSystem.DataContext; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.psi.PsiManager; 11 | import de.espend.idea.oxid.OxidProjectComponent; 12 | import de.espend.idea.oxid.utils.SmartyBlockUtil; 13 | import de.espend.idea.oxid.utils.SmartyPattern; 14 | import de.espend.idea.oxid.utils.TemplateUtil; 15 | import de.espend.idea.oxid.utils.TranslationUtil; 16 | import org.apache.commons.lang.StringUtils; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Collection; 22 | 23 | /** 24 | * @author Daniel Espendiller 25 | */ 26 | public class SmartyGoToHandler implements GotoDeclarationHandler { 27 | @Nullable 28 | @Override 29 | public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement psiElement, int i, Editor editor) { 30 | 31 | if(!OxidProjectComponent.isValidForProject(psiElement)) { 32 | return new PsiElement[0]; 33 | } 34 | 35 | Collection psiElements = new ArrayList(); 36 | 37 | if(SmartyPattern.getFilePattern().accepts(psiElement)) { 38 | attachFiles(psiElements, psiElement); 39 | } 40 | 41 | if(SmartyPattern.getBlockPattern().accepts(psiElement)) { 42 | attachBlocks(psiElements, psiElement); 43 | } 44 | 45 | if(SmartyPattern.getAttributeInsideTagPattern("ident", "oxmultilang").accepts(psiElement)) { 46 | attachTranslations(psiElements, psiElement); 47 | } 48 | 49 | if(SmartyPattern.getAttributeInsideTagPattern("ident", "oxcontent").accepts(psiElement)) { 50 | attachContentIdent(psiElements, psiElement); 51 | } 52 | 53 | return psiElements.toArray(new PsiElement[psiElements.size()]); 54 | } 55 | 56 | private void attachContentIdent(@NotNull Collection psiElements, @NotNull PsiElement psiElement) { 57 | final String contents = psiElement.getText(); 58 | if(StringUtils.isBlank(contents)) { 59 | return; 60 | } 61 | 62 | psiElements.addAll(TemplateUtil.getContentIdentsTargets(psiElement.getProject(), psiElement.getContainingFile().getVirtualFile(), contents)); 63 | } 64 | 65 | private void attachTranslations(@NotNull Collection psiElements, @NotNull PsiElement psiElement) { 66 | 67 | final String contents = psiElement.getText(); 68 | if(StringUtils.isBlank(contents)) { 69 | return; 70 | } 71 | 72 | psiElements.addAll(TranslationUtil.getTranslationTargets(psiElement.getProject(), contents)); 73 | } 74 | 75 | private void attachBlocks(@NotNull Collection psiElements, @NotNull PsiElement psiElement) { 76 | 77 | final String contents = psiElement.getText(); 78 | if(StringUtils.isBlank(contents)) { 79 | return; 80 | } 81 | 82 | for (String templateName: TemplateUtil.getTemplateNames(psiElement.getProject(), psiElement.getContainingFile().getVirtualFile())) { 83 | for (SmartyBlockUtil.SmartyBlock block : TemplateUtil.getBlocksTemplateName(psiElement.getProject(), templateName)) { 84 | if(block.getName().equals(contents)) { 85 | psiElements.add(block.getElement()); 86 | } 87 | } 88 | } 89 | } 90 | 91 | private void attachFiles(@NotNull Collection psiElements, @NotNull PsiElement psiElement) { 92 | 93 | final String text = psiElement.getText(); 94 | if(StringUtils.isBlank(text)) { 95 | return; 96 | } 97 | 98 | attachTemplateFiles(psiElement.getProject(), text, psiElements); 99 | } 100 | 101 | @Nullable 102 | @Override 103 | public String getActionText(DataContext dataContext) { 104 | return null; 105 | } 106 | 107 | private void attachTemplateFiles(final Project project, final @NotNull String s, final @NotNull Collection psiElements) { 108 | TemplateUtil.collectFiles(project, new TemplateUtil.SmartyTemplateVisitor() { 109 | @Override 110 | public void visitFile(VirtualFile virtualFile, String fileName) { 111 | if(s.equals(fileName)) { 112 | PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); 113 | if(file != null) { 114 | psiElements.add(file); 115 | } 116 | } 117 | } 118 | }); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/navigation/SmartyTemplateLineMarkerProvider.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.navigation; 2 | 3 | import com.intellij.codeInsight.daemon.LineMarkerInfo; 4 | import com.intellij.codeInsight.daemon.LineMarkerProvider; 5 | import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo; 6 | import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; 7 | import com.intellij.navigation.GotoRelatedItem; 8 | import com.intellij.openapi.vfs.VfsUtil; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.intellij.psi.PsiElement; 11 | import com.intellij.psi.PsiFile; 12 | import com.intellij.psi.PsiManager; 13 | import com.intellij.util.ConstantFunction; 14 | import com.jetbrains.php.PhpIcons; 15 | import com.jetbrains.smarty.SmartyFile; 16 | import de.espend.idea.oxid.OxidPluginIcons; 17 | import de.espend.idea.oxid.OxidProjectComponent; 18 | import de.espend.idea.oxid.utils.SmartyBlockUtil; 19 | import de.espend.idea.oxid.utils.SmartyPattern; 20 | import de.espend.idea.oxid.utils.TemplateUtil; 21 | import fr.adrienbrault.idea.symfony2plugin.dic.RelatedPopupGotoLineMarker; 22 | import org.apache.commons.lang.StringUtils; 23 | import org.jetbrains.annotations.NotNull; 24 | import org.jetbrains.annotations.Nullable; 25 | 26 | import javax.swing.*; 27 | import java.util.*; 28 | 29 | /** 30 | * @author Daniel Espendiller 31 | */ 32 | public class SmartyTemplateLineMarkerProvider implements LineMarkerProvider { 33 | 34 | @Nullable 35 | @Override 36 | public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement psiElement) { 37 | return null; 38 | } 39 | 40 | @Override 41 | public void collectSlowLineMarkers(@NotNull List psiElements, @NotNull Collection collection) { 42 | 43 | if(psiElements.size() == 0 || !OxidProjectComponent.isValidForProject(psiElements.get(0))) { 44 | return; 45 | } 46 | 47 | for(PsiElement psiElement: psiElements) { 48 | 49 | if (psiElement instanceof SmartyFile) { 50 | attachFileContextMaker((SmartyFile) psiElement, collection); 51 | } 52 | 53 | if(SmartyPattern.getBlockPattern().accepts(psiElement)) { 54 | attachBlocks(psiElement, collection); 55 | } 56 | 57 | } 58 | } 59 | 60 | private void attachBlocks(PsiElement psiElement, Collection lineMarkerInfos) { 61 | 62 | final String contents = psiElement.getText(); 63 | if(StringUtils.isBlank(contents)) { 64 | return; 65 | } 66 | 67 | VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile(); 68 | 69 | Collection psiElements = new ArrayList(); 70 | 71 | for (String templateName: TemplateUtil.getTemplateNames(psiElement.getProject(), psiElement.getContainingFile().getVirtualFile())) { 72 | for (SmartyBlockUtil.SmartyBlock block : TemplateUtil.getBlocksTemplateName(psiElement.getProject(), templateName)) { 73 | if(block.getName().equals(contents)) { 74 | if(!virtualFile.equals(block.getElement().getContainingFile().getVirtualFile())) { 75 | psiElements.add(block.getElement()); 76 | } 77 | } 78 | } 79 | } 80 | 81 | if(psiElements.size() == 0) { 82 | return; 83 | } 84 | 85 | NavigationGutterIconBuilder builder = NavigationGutterIconBuilder.create(PhpIcons.OVERRIDES). 86 | setTargets(psiElements). 87 | setTooltipText("Navigate to block"); 88 | 89 | lineMarkerInfos.add(builder.createLineMarkerInfo(psiElement)); 90 | } 91 | 92 | private void attachFileContextMaker(SmartyFile smartyFile, @NotNull Collection lineMarkerInfo) { 93 | 94 | final VirtualFile virtualFile = smartyFile.getVirtualFile(); 95 | 96 | final Set templates = TemplateUtil.getTemplateNames(smartyFile.getProject(), virtualFile); 97 | if(templates.size() == 0) { 98 | return; 99 | } 100 | 101 | List gotoRelatedItems = new ArrayList(); 102 | 103 | for (String template : templates) { 104 | for (VirtualFile file : TemplateUtil.getFilesByTemplateName(smartyFile.getProject(), template)) { 105 | 106 | if(file.equals(virtualFile)) { 107 | continue; 108 | } 109 | 110 | PsiFile psiFile = PsiManager.getInstance(smartyFile.getProject()).findFile(file); 111 | if(psiFile != null) { 112 | String templateName = getPresentableTemplateName(smartyFile, file); 113 | gotoRelatedItems.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(psiFile, templateName).withIcon(file.getFileType().getIcon(), OxidPluginIcons.OXID_LINEMARKER)); 114 | } 115 | } 116 | } 117 | 118 | if(gotoRelatedItems.size() == 0) { 119 | return; 120 | } 121 | 122 | // only one item dont need popover 123 | if(gotoRelatedItems.size() == 1) { 124 | lineMarkerInfo.add(getSingleLineMarker(smartyFile, lineMarkerInfo, gotoRelatedItems.get(0))); 125 | return; 126 | } 127 | 128 | if(gotoRelatedItems.size() == 0) { 129 | return; 130 | } 131 | 132 | lineMarkerInfo.add(getRelatedPopover("Overwrite", "Overwrites", smartyFile, gotoRelatedItems)); 133 | } 134 | 135 | private LineMarkerInfo getRelatedPopover(String singleItemTitle, String singleItemTooltipPrefix, PsiElement lineMarkerTarget, List gotoRelatedItems) { 136 | 137 | // single item has no popup 138 | String title = singleItemTitle; 139 | if(gotoRelatedItems.size() == 1) { 140 | String customName = gotoRelatedItems.get(0).getCustomName(); 141 | if(customName != null) { 142 | title = String.format(singleItemTooltipPrefix, customName); 143 | } 144 | } 145 | 146 | return new LineMarkerInfo(lineMarkerTarget, lineMarkerTarget.getTextOffset(), OxidPluginIcons.OXID_LINEMARKER, 6, new ConstantFunction(title), new fr.adrienbrault.idea.symfony2plugin.dic.RelatedPopupGotoLineMarker.NavigationHandler(gotoRelatedItems)); 147 | } 148 | 149 | public static RelatedItemLineMarkerInfo getSingleLineMarker(SmartyFile smartyFile, Collection lineMarkerInfos, GotoRelatedItem gotoRelatedItem) { 150 | 151 | // hell: find any possible small icon 152 | Icon icon = null; 153 | if(gotoRelatedItem instanceof RelatedPopupGotoLineMarker.PopupGotoRelatedItem) { 154 | icon = ((RelatedPopupGotoLineMarker.PopupGotoRelatedItem) gotoRelatedItem).getSmallIcon(); 155 | } 156 | 157 | if(icon == null) { 158 | icon = OxidPluginIcons.OXID_LINEMARKER; 159 | } 160 | 161 | NavigationGutterIconBuilder builder = NavigationGutterIconBuilder.create(icon). 162 | setTargets(gotoRelatedItem.getElement()); 163 | 164 | String customName = gotoRelatedItem.getCustomName(); 165 | if(customName != null) { 166 | builder.setTooltipText(customName); 167 | } 168 | 169 | return builder.createLineMarkerInfo(smartyFile); 170 | } 171 | 172 | private String getPresentableTemplateName(SmartyFile smartyFile, VirtualFile file) { 173 | String templateName = file.getPath(); 174 | 175 | String relativePath = VfsUtil.getRelativePath(file, smartyFile.getProject().getBaseDir()); 176 | if(relativePath != null) { 177 | templateName = relativePath; 178 | } 179 | 180 | int i = templateName.indexOf("/views/"); 181 | if(i > 0) { 182 | templateName = templateName.substring(i + "/views/".length()); 183 | } 184 | 185 | if(templateName.length() > 50) { 186 | templateName = "..." + templateName.substring(templateName.length() - 50); 187 | } 188 | return templateName; 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/stub/OxidContentIdentIndexer.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.stub; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import com.intellij.psi.PsiFile; 5 | import com.intellij.psi.PsiRecursiveElementWalkingVisitor; 6 | import com.intellij.util.indexing.*; 7 | import com.intellij.util.io.DataExternalizer; 8 | import com.intellij.util.io.EnumeratorStringDescriptor; 9 | import com.intellij.util.io.KeyDescriptor; 10 | import com.intellij.util.io.VoidDataExternalizer; 11 | import com.jetbrains.smarty.SmartyFileType; 12 | import de.espend.idea.oxid.utils.SmartyPattern; 13 | import gnu.trove.THashMap; 14 | import org.apache.commons.lang.StringUtils; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.util.Map; 18 | 19 | /** 20 | * @author Daniel Espendiller 21 | */ 22 | public class OxidContentIdentIndexer extends FileBasedIndexExtension { 23 | 24 | public static final ID KEY = ID.create("de.espend.idea.oxid.oxid_content_ident"); 25 | private final KeyDescriptor myKeyDescriptor = new EnumeratorStringDescriptor(); 26 | 27 | @NotNull 28 | @Override 29 | public ID getName() { 30 | return KEY; 31 | } 32 | 33 | @NotNull 34 | @Override 35 | public DataIndexer getIndexer() { 36 | 37 | return new DataIndexer() { 38 | @NotNull 39 | @Override 40 | public Map map(@NotNull FileContent inputData) { 41 | 42 | PsiFile psiFile = inputData.getPsiFile(); 43 | final Map map = new THashMap<>(); 44 | 45 | psiFile.acceptChildren(new PsiRecursiveElementWalkingVisitor() { 46 | @Override 47 | public void visitElement(PsiElement element) { 48 | 49 | if(SmartyPattern.getAttributeInsideTagPattern("ident", "oxcontent").accepts(element)) { 50 | String content = element.getText(); 51 | if(StringUtils.isNotBlank(content)) { 52 | map.put(content, null); 53 | } 54 | } 55 | 56 | super.visitElement(element); 57 | } 58 | 59 | }); 60 | 61 | return map; 62 | } 63 | }; 64 | 65 | } 66 | 67 | @NotNull 68 | @Override 69 | public KeyDescriptor getKeyDescriptor() { 70 | return this.myKeyDescriptor; 71 | } 72 | 73 | @NotNull 74 | @Override 75 | public DataExternalizer getValueExternalizer() { 76 | return VoidDataExternalizer.INSTANCE; 77 | } 78 | 79 | @NotNull 80 | @Override 81 | public FileBasedIndex.InputFilter getInputFilter() { 82 | return file -> file.getFileType() == SmartyFileType.INSTANCE; 83 | } 84 | 85 | @Override 86 | public boolean dependsOnFileContent() { 87 | return true; 88 | } 89 | 90 | @Override 91 | public int getVersion() { 92 | return 1; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/types/OxidFactoryTypeProvider.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.types; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.psi.PsiFile; 7 | import com.intellij.psi.PsiManager; 8 | import com.jetbrains.php.PhpIndex; 9 | import com.jetbrains.php.lang.psi.PhpFile; 10 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 11 | import com.jetbrains.php.lang.psi.elements.*; 12 | import com.jetbrains.php.lang.psi.resolve.types.PhpType; 13 | import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider3; 14 | import de.espend.idea.oxid.utils.ModuleUtil; 15 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; 16 | import fr.adrienbrault.idea.symfony2plugin.util.PhpTypeProviderUtil; 17 | import org.apache.commons.lang.StringUtils; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.util.*; 21 | 22 | public class OxidFactoryTypeProvider implements PhpTypeProvider3 { 23 | final private static char TRIM_KEY = '\u0180'; 24 | 25 | @Override 26 | public char getKey() { 27 | return '\u0169'; 28 | } 29 | 30 | @Nullable 31 | @Override 32 | public PhpType getType(PsiElement e) { 33 | if(e instanceof FunctionReference && "oxNew".equalsIgnoreCase(((FunctionReference) e).getName())) { 34 | PsiElement[] parameters = ((FunctionReference) e).getParameters(); 35 | if(parameters.length > 0 && parameters[0] instanceof StringLiteralExpression) { 36 | String contents = ((StringLiteralExpression) parameters[0]).getContents(); 37 | if(StringUtils.isNotBlank(contents)) { 38 | String signature = PhpTypeProviderUtil.getReferenceSignatureByFirstParameter((FunctionReference) e, TRIM_KEY); 39 | return signature == null ? null : new PhpType().add("#" + this.getKey() + signature); 40 | } 41 | } 42 | } 43 | 44 | // container calls are only on "get" methods 45 | if(e instanceof MethodReference && PhpElementsUtil.isMethodWithFirstStringOrFieldReference(e, "get")) { 46 | String signature = PhpTypeProviderUtil.getReferenceSignatureByFirstParameter((FunctionReference) e, TRIM_KEY); 47 | return signature == null ? null : new PhpType().add("#" + this.getKey() + signature); 48 | } 49 | 50 | return null; 51 | } 52 | 53 | @Override 54 | public Collection getBySignature(String expression, Set visited, int depth, Project project) { 55 | 56 | int endIndex = expression.lastIndexOf(TRIM_KEY); 57 | if(endIndex == -1) { 58 | return Collections.emptySet(); 59 | } 60 | 61 | String originalSignature = expression.substring(0, endIndex); 62 | String parameter = expression.substring(endIndex + 1); 63 | 64 | // search for called method 65 | PhpIndex phpIndex = PhpIndex.getInstance(project); 66 | Collection phpNamedElementCollections = phpIndex.getBySignature(originalSignature, null, 0); 67 | if(phpNamedElementCollections.size() == 0) { 68 | return Collections.emptySet(); 69 | } 70 | 71 | // get first matched item 72 | PhpNamedElement phpNamedElement = phpNamedElementCollections.iterator().next(); 73 | if(!(phpNamedElement instanceof Function)) { 74 | return phpNamedElementCollections; 75 | } 76 | 77 | parameter = PhpTypeProviderUtil.getResolvedParameter(phpIndex, parameter); 78 | if(parameter == null) { 79 | return phpNamedElementCollections; 80 | } 81 | 82 | PhpClass phpClass = PhpElementsUtil.getClassInterface(project, parameter); 83 | if(phpClass != null) { 84 | 85 | 86 | Collection phpClasses = new ArrayList(); 87 | phpClasses.add(phpClass); 88 | 89 | addExtendsClasses(project, parameter, phpClasses); 90 | 91 | return phpClasses; 92 | } 93 | 94 | return null; 95 | } 96 | 97 | /** 98 | * 99 | * We support "extends" on module metadata 100 | * 101 | * TODO: use index, this in a performance issue 102 | */ 103 | private void addExtendsClasses(Project project, String parameter, Collection phpClasses) { 104 | 105 | for (Map.Entry> entry : ModuleUtil.getExtendsList(project).entrySet()) { 106 | 107 | // ignore cases, so we need an each 108 | if(!entry.getKey().equalsIgnoreCase(parameter)) { 109 | continue; 110 | } 111 | 112 | for (VirtualFile virtualFile : entry.getValue()) { 113 | 114 | PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); 115 | if (!(file instanceof PhpFile)) { 116 | continue; 117 | } 118 | 119 | Collection allClasses = PhpPsiUtil.findAllClasses((PhpFile) file); 120 | if (allClasses.size() > 0) { 121 | phpClasses.add(allClasses.iterator().next()); 122 | } 123 | 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/MetadataUtil.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | import com.intellij.patterns.PlatformPatterns; 5 | import com.intellij.patterns.PsiElementPattern; 6 | import com.intellij.psi.PsiElement; 7 | import com.intellij.psi.PsiFile; 8 | import com.intellij.psi.util.PsiTreeUtil; 9 | import com.jetbrains.php.lang.parser.PhpElementTypes; 10 | import com.jetbrains.php.lang.psi.elements.ArrayCreationExpression; 11 | import com.jetbrains.php.lang.psi.elements.GroupStatement; 12 | import com.jetbrains.php.lang.psi.elements.PhpPsiElement; 13 | import com.jetbrains.php.lang.psi.elements.Statement; 14 | import com.jetbrains.php.lang.psi.elements.impl.AssignmentExpressionImpl; 15 | import de.espend.idea.oxid.dict.metadata.MetadataBlock; 16 | import de.espend.idea.oxid.dict.metadata.MetadataSetting; 17 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; 18 | import org.apache.commons.lang.StringUtils; 19 | import org.jetbrains.annotations.NotNull; 20 | import org.jetbrains.annotations.Nullable; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collection; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * @author Daniel Espendiller 29 | */ 30 | public class MetadataUtil { 31 | 32 | public static PsiElementPattern.Capture getMetadataFilePattern() { 33 | return PlatformPatterns.psiElement().inVirtualFile(PlatformPatterns.virtualFile().withName("metadata.php")); 34 | } 35 | 36 | public static Collection getSettings(@NotNull PsiFile psiFile) { 37 | 38 | final Collection blocks = new ArrayList(); 39 | 40 | visitMetadataKey(psiFile, "settings", new MetadataKeyVisitor() { 41 | @Override 42 | public void visit(@NotNull ArrayCreationExpression arrayCreationExpression) { 43 | 44 | for (PhpPsiElement phpPsiElement : PsiTreeUtil.getChildrenOfTypeAsList(arrayCreationExpression, PhpPsiElement.class)) { 45 | 46 | if(phpPsiElement.getNode().getElementType() != PhpElementTypes.ARRAY_VALUE) { 47 | continue; 48 | } 49 | 50 | PhpPsiElement firstPsiChild = phpPsiElement.getFirstPsiChild(); 51 | if(!(firstPsiChild instanceof ArrayCreationExpression)) { 52 | continue; 53 | } 54 | 55 | MetadataSetting block = MetadataSetting.create(firstPsiChild, getArrayKeyValueMapProxy((ArrayCreationExpression) firstPsiChild)); 56 | if(block != null) { 57 | blocks.add(block); 58 | } 59 | 60 | } 61 | 62 | } 63 | }); 64 | 65 | return blocks; 66 | } 67 | 68 | public static Collection getBlocks(@NotNull PsiFile psiFile) { 69 | 70 | final Collection blocks = new ArrayList(); 71 | 72 | visitMetadataKey(psiFile, "blocks", new MetadataKeyVisitor() { 73 | @Override 74 | public void visit(@NotNull ArrayCreationExpression arrayCreationExpression) { 75 | 76 | for (PhpPsiElement phpPsiElement : PsiTreeUtil.getChildrenOfTypeAsList(arrayCreationExpression, PhpPsiElement.class)) { 77 | 78 | if(phpPsiElement.getNode().getElementType() != PhpElementTypes.ARRAY_VALUE) { 79 | continue; 80 | } 81 | 82 | PhpPsiElement firstPsiChild = phpPsiElement.getFirstPsiChild(); 83 | if(!(firstPsiChild instanceof ArrayCreationExpression)) { 84 | continue; 85 | } 86 | 87 | MetadataBlock block = MetadataBlock.create(getArrayKeyValueMapProxy((ArrayCreationExpression) firstPsiChild)); 88 | if(block != null) { 89 | blocks.add(block); 90 | } 91 | 92 | } 93 | 94 | } 95 | }); 96 | 97 | return blocks; 98 | } 99 | 100 | public static Map getMetadataKeyMap(@NotNull PsiFile psiFile, @NotNull String key) { 101 | 102 | final Map values = new HashMap(); 103 | 104 | visitMetadataKey(psiFile, key, new MetadataKeyVisitor() { 105 | @Override 106 | public void visit(@NotNull ArrayCreationExpression arrayCreationExpression) { 107 | values.putAll(getArrayKeyValueMapProxy(arrayCreationExpression)); 108 | } 109 | }); 110 | 111 | return values; 112 | } 113 | 114 | 115 | /** 116 | * GetArrayKeyValueMap adds null Value; proxy until fixed external 117 | */ 118 | @NotNull 119 | private static Map getArrayKeyValueMapProxy(@NotNull ArrayCreationExpression arrayCreationExpression) { 120 | 121 | Map map = new HashMap(); 122 | 123 | for (Map.Entry entry : PhpElementsUtil.getArrayKeyValueMap(arrayCreationExpression).entrySet()) { 124 | String value = entry.getValue(); 125 | String key = entry.getKey(); 126 | if(key != null && StringUtils.isNotBlank(key) && value != null && StringUtils.isNotBlank(value)) { 127 | map.put(entry.getKey(), entry.getValue()); 128 | } 129 | } 130 | 131 | return map; 132 | } 133 | 134 | private static void visitMetadataKey(@NotNull PsiFile psiFile, @NotNull String key, @NotNull MetadataKeyVisitor visitor) { 135 | 136 | PsiElement childOfType = PsiTreeUtil.getChildOfType(psiFile, GroupStatement.class); 137 | if(childOfType == null) { 138 | return; 139 | } 140 | 141 | for (Statement statement : PsiTreeUtil.getChildrenOfTypeAsList(childOfType, Statement.class)) { 142 | PsiElement assignmentExpr = statement.getFirstPsiChild(); 143 | if(assignmentExpr instanceof AssignmentExpressionImpl) { 144 | PhpPsiElement variable = ((AssignmentExpressionImpl) assignmentExpr).getVariable(); 145 | if(variable != null && "aModule".equals(variable.getName())) { 146 | 147 | PhpPsiElement value = ((AssignmentExpressionImpl) assignmentExpr).getValue(); 148 | if(value instanceof ArrayCreationExpression) { 149 | PhpPsiElement arrayCreationKeyMap = PhpElementsUtil.getArrayValue((ArrayCreationExpression) value, key); 150 | if(arrayCreationKeyMap instanceof ArrayCreationExpression) { 151 | visitor.visit((ArrayCreationExpression) arrayCreationKeyMap); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | public static void visitTranslationKey(@NotNull PsiFile psiFile, @NotNull TranslationKeyVisitor visitor) { 160 | 161 | PsiElement childOfType = PsiTreeUtil.getChildOfType(psiFile, GroupStatement.class); 162 | if(childOfType == null) { 163 | return; 164 | } 165 | 166 | Statement[] childrenOfType = PsiTreeUtil.getChildrenOfType(childOfType, Statement.class); 167 | if(childrenOfType == null) { 168 | return; 169 | } 170 | 171 | for (Statement statement : childrenOfType) { 172 | PsiElement assignmentExpr = statement.getFirstPsiChild(); 173 | if(assignmentExpr instanceof AssignmentExpressionImpl) { 174 | PhpPsiElement variable = ((AssignmentExpressionImpl) assignmentExpr).getVariable(); 175 | if(variable != null && "aLang".equals(variable.getName())) { 176 | 177 | PhpPsiElement value = ((AssignmentExpressionImpl) assignmentExpr).getValue(); 178 | if(value instanceof ArrayCreationExpression) { 179 | for (Map.Entry entry : PhpElementsUtil.getArrayCreationKeyMap((ArrayCreationExpression) value).entrySet()) { 180 | visitor.visit(entry.getKey(), entry.getValue()); 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } 187 | 188 | public interface TranslationKeyVisitor { 189 | void visit(@NotNull String name, @NotNull PsiElement value); 190 | } 191 | 192 | public interface MetadataKeyVisitor { 193 | void visit(@NotNull ArrayCreationExpression arrayCreationExpression); 194 | } 195 | 196 | /** 197 | * Find modules folder on "modules" or vendor structure 198 | * 199 | * @param file metadata file 200 | */ 201 | @Nullable 202 | public static VirtualFile getModuleVendorFolderFromMetadata(@NotNull VirtualFile file) { 203 | 204 | // save previous we 205 | VirtualFile current = file; 206 | 207 | for (VirtualFile parent = file.getParent(); parent != null; parent = parent.getParent()) { 208 | 209 | if(parent.getName().equals("modules")) { 210 | return current; 211 | } 212 | 213 | if(parent.isDirectory() && parent.findChild("vendormetadata.php") != null) { 214 | return parent; 215 | } 216 | 217 | // we need to return previous PsiElement 218 | current = parent; 219 | } 220 | 221 | return null; 222 | } 223 | 224 | /** 225 | * getModuleVendorFolderFromMetadata delivers our topmost "vendor module" folder 226 | * We need to get the parent here, this should be our main path root 227 | */ 228 | @Nullable 229 | public static VirtualFile getModuleDirectoryOnMetadata(@NotNull VirtualFile file) { 230 | 231 | VirtualFile modulesDir = getModuleVendorFolderFromMetadata(file); 232 | if(modulesDir == null) { 233 | return null; 234 | } 235 | 236 | // should be "modules" folder 237 | return modulesDir.getParent(); 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/ModuleUtil.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.Key; 5 | import com.intellij.openapi.vfs.VfsUtil; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.intellij.openapi.vfs.VirtualFileVisitor; 8 | import com.intellij.psi.PsiDirectory; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.psi.search.FilenameIndex; 11 | import com.intellij.psi.search.GlobalSearchScope; 12 | import com.intellij.psi.util.CachedValue; 13 | import com.intellij.psi.util.CachedValueProvider; 14 | import com.intellij.psi.util.CachedValuesManager; 15 | import com.intellij.psi.util.PsiModificationTracker; 16 | import com.jetbrains.smarty.SmartyFileType; 17 | import org.apache.commons.lang.StringUtils; 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | import java.util.HashMap; 22 | import java.util.HashSet; 23 | import java.util.Map; 24 | import java.util.Set; 25 | 26 | /** 27 | * @author Daniel Espendiller 28 | */ 29 | public class ModuleUtil { 30 | 31 | private static final Key>>> EXTEND_LIST_CACHE = new Key>>>("OXID_EXTEND_LIST_CACHE"); 32 | 33 | public static void visitModuleTemplatesInMetadataScope(@NotNull PsiFile metaFile, final @NotNull ModuleFileVisitor visitor) { 34 | 35 | ModuleUtil.visitModuleFile(metaFile, new ModuleUtil.ModuleFileVisitor() { 36 | @Override 37 | public void visit(@NotNull VirtualFile virtualFile, @NotNull String relativePath) { 38 | 39 | if (virtualFile.getFileType() != SmartyFileType.INSTANCE) { 40 | return; 41 | } 42 | 43 | String[] split = relativePath.split("/"); 44 | if(split.length <= 2) { 45 | return; 46 | } 47 | 48 | int n = split.length - 2; 49 | String[] newArray = new String[n]; 50 | System.arraycopy(split, 2, newArray, 0, n); 51 | 52 | visitor.visit(virtualFile, StringUtils.join(newArray, "/")); 53 | } 54 | }); 55 | } 56 | 57 | public static void visitModuleFile(@NotNull PsiFile metaFile, final @NotNull ModuleFileVisitor visitor) { 58 | 59 | final VirtualFile moduleFolder = MetadataUtil.getModuleVendorFolderFromMetadata(metaFile.getVirtualFile()); 60 | if(moduleFolder == null) { 61 | return; 62 | } 63 | 64 | PsiDirectory parent = metaFile.getParent(); 65 | if(parent == null) { 66 | return; 67 | } 68 | 69 | VfsUtil.visitChildrenRecursively(parent.getVirtualFile(), new VirtualFileVisitor() { 70 | @Override 71 | public boolean visitFile(@NotNull VirtualFile file) { 72 | 73 | if (file.isDirectory()) { 74 | return super.visitFile(file); 75 | } 76 | 77 | String relativePath = VfsUtil.getRelativePath(file, moduleFolder.getParent(), '/'); 78 | if(relativePath == null) { 79 | return super.visitFile(file); 80 | } 81 | 82 | visitor.visit(file, relativePath); 83 | 84 | return super.visitFile(file); 85 | } 86 | }); 87 | } 88 | 89 | public interface ModuleFileVisitor { 90 | void visit(@NotNull VirtualFile virtualFile, @NotNull String relativePath); 91 | } 92 | 93 | @NotNull 94 | synchronized public static Map> getExtendsList(@NotNull final Project project) { 95 | 96 | CachedValue>> cache = project.getUserData(EXTEND_LIST_CACHE); 97 | 98 | if (cache == null) { 99 | cache = CachedValuesManager.getManager(project).createCachedValue(new CachedValueProvider>>() { 100 | @Nullable 101 | @Override 102 | public Result>> compute() { 103 | 104 | Map> extendsList = new HashMap>(); 105 | 106 | for (PsiFile psiFile : FilenameIndex.getFilesByName(project, "metadata.php", GlobalSearchScope.allScope(project))) { 107 | 108 | VirtualFile vendorDir = MetadataUtil.getModuleDirectoryOnMetadata(psiFile.getVirtualFile()); 109 | if(vendorDir == null) { 110 | continue; 111 | } 112 | 113 | for (Map.Entry entry : MetadataUtil.getMetadataKeyMap(psiFile, "extend").entrySet()) { 114 | if(!extendsList.containsKey(entry.getKey())) { 115 | extendsList.put(entry.getKey(), new HashSet()); 116 | } 117 | 118 | String replace = entry.getValue().replace("\\", "/"); 119 | if(replace.startsWith("/")) { 120 | replace = replace.substring(1); 121 | } 122 | 123 | if(!replace.toLowerCase().endsWith(".php")) { 124 | replace = replace + ".php"; 125 | } 126 | 127 | VirtualFile relativeFile = VfsUtil.findRelativeFile(vendorDir, replace.split("/")); 128 | if(relativeFile != null) { 129 | extendsList.get(entry.getKey()).add(relativeFile); 130 | } 131 | } 132 | } 133 | 134 | return Result.create(extendsList, PsiModificationTracker.MODIFICATION_COUNT); 135 | } 136 | }, false); 137 | project.putUserData(EXTEND_LIST_CACHE, cache); 138 | } 139 | 140 | return cache.getValue(); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/OxidUtil.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; 4 | import com.intellij.codeInsight.lookup.LookupElement; 5 | import com.intellij.ide.plugins.PluginManager; 6 | import com.intellij.openapi.application.ApplicationInfo; 7 | import com.intellij.openapi.application.ApplicationManager; 8 | import com.intellij.openapi.application.Result; 9 | import com.intellij.openapi.command.WriteCommandAction; 10 | import com.intellij.openapi.extensions.PluginId; 11 | import com.intellij.openapi.project.Project; 12 | import com.intellij.openapi.util.Condition; 13 | import com.intellij.openapi.vfs.VfsUtil; 14 | import com.intellij.openapi.vfs.VirtualFile; 15 | import com.intellij.psi.PsiDirectory; 16 | import com.intellij.psi.PsiElement; 17 | import com.intellij.psi.PsiFile; 18 | import com.intellij.psi.PsiManager; 19 | import com.intellij.psi.codeStyle.CodeStyleManager; 20 | import com.intellij.util.containers.ContainerUtil; 21 | import com.jetbrains.php.PhpIndex; 22 | import com.jetbrains.php.completion.PhpClassLookupElement; 23 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; 24 | import com.jetbrains.php.lang.psi.PhpFile; 25 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory; 26 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 27 | import com.jetbrains.php.lang.psi.elements.*; 28 | import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; 29 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; 30 | import fr.adrienbrault.idea.symfony2plugin.util.completion.PhpClassReferenceInsertHandler; 31 | import org.apache.commons.lang.StringUtils; 32 | import org.jetbrains.annotations.NotNull; 33 | import org.jetbrains.annotations.Nullable; 34 | 35 | import java.text.SimpleDateFormat; 36 | import java.util.*; 37 | 38 | /** 39 | * @author Daniel Espendiller 40 | */ 41 | public class OxidUtil { 42 | 43 | private static final String PHPSTORM_OXID_META_PHP = ".phpstorm-oxid.meta.php"; 44 | 45 | public static boolean isFactory(@NotNull StringLiteralExpression literalExpression) { 46 | 47 | PsiElement parameterList = literalExpression.getParent(); 48 | if(parameterList instanceof ParameterList) { 49 | PsiElement function = parameterList.getParent(); 50 | if(function instanceof FunctionReference) { 51 | if("oxNew".equalsIgnoreCase(((FunctionReference) function).getName())) { 52 | return true; 53 | } 54 | } 55 | } 56 | 57 | return new MethodMatcher.StringParameterRecursiveMatcher(literalExpression, 0) 58 | .withSignature("\\oxRegistry", "get") 59 | .match() != null; 60 | } 61 | 62 | @Nullable 63 | public static String getPseudoClassOverwrites(@NotNull Project project) { 64 | 65 | StringBuilder s = new StringBuilder(); 66 | 67 | Map traits = new HashMap(); 68 | 69 | for (Map.Entry> entry : ModuleUtil.getExtendsList(project).entrySet()) { 70 | 71 | String key = entry.getKey(); 72 | PhpClass parentClass = PhpElementsUtil.getClassInterface(project, key); 73 | if(parentClass == null) { 74 | continue; 75 | } 76 | 77 | String className = parentClass.getPresentableFQN(); 78 | if(className == null) { 79 | continue; 80 | } 81 | 82 | Map subClasses = new HashMap(); 83 | 84 | for (VirtualFile virtualFile : entry.getValue()) { 85 | 86 | PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); 87 | if(!(file instanceof PhpFile)) { 88 | continue; 89 | } 90 | 91 | Collection allClasses = PhpPsiUtil.findAllClasses((PhpFile) file); 92 | if(allClasses.size() == 0) { 93 | continue; 94 | } 95 | 96 | PhpClass phpClass = allClasses.iterator().next(); 97 | ExtendsList extendsList = phpClass.getExtendsList(); 98 | List referenceElements = extendsList.getReferenceElements(); 99 | if(referenceElements != null && referenceElements.size() > 0) { 100 | ClassReference next = referenceElements.iterator().next(); 101 | 102 | String fqn = next.getFQN(); 103 | if(fqn == null) { 104 | continue; 105 | } 106 | 107 | if(fqn.startsWith("\\")) { 108 | fqn = fqn.substring(1); 109 | } 110 | 111 | if(subClasses.containsKey(fqn)) { 112 | continue; 113 | } 114 | 115 | String originClass = phpClass.getFQN(); 116 | if(originClass == null) { 117 | continue; 118 | } 119 | 120 | subClasses.put(fqn, originClass); 121 | 122 | if(originClass.startsWith("\\")) { 123 | originClass = originClass.substring(1); 124 | } 125 | 126 | traits.put(originClass, getDummyTraitContent(phpClass)); 127 | } 128 | 129 | } 130 | 131 | for (final Map.Entry entryC : subClasses.entrySet()) { 132 | s.append(String.format("class %s extends %s { %s }\n", entryC.getKey(), className, buildTraitUses(subClasses, entryC))); 133 | } 134 | 135 | } 136 | 137 | String content = s.toString(); 138 | if(StringUtils.isBlank(content)) { 139 | return null; 140 | } 141 | 142 | for (Map.Entry entry : traits.entrySet()) { 143 | content += String.format("trait %sTrait {\n %s \n }\n", entry.getKey(), entry.getValue()); 144 | } 145 | 146 | if(StringUtils.isBlank(content)) { 147 | return null; 148 | } 149 | 150 | String info = String.format("%s / %s / OXID Plugin %s", 151 | ApplicationInfo.getInstance().getVersionName(), 152 | ApplicationInfo.getInstance().getBuild(), 153 | PluginManager.getPlugin(PluginId.getId("de.espend.idea.oxid")).getVersion() 154 | ); 155 | 156 | return "/**\n" + 157 | " * An helper file for OXID, to provide autocomplete information to your IDE\n" + 158 | " * Generated with " + info + " on " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime()) + ".\n" + 159 | " *\n" + 160 | " * @author Daniel Espendiller \n" + 161 | " * @see https://github.com/Haehnchen/idea-php-oxid-plugin\n" + 162 | " */\n" + 163 | "\n" + 164 | "namespace {\n" + 165 | " exit(\"This file should not be included, only analyzed by your IDE\");\n" + 166 | content + 167 | "\n}"; 168 | } 169 | 170 | private static String getDummyTraitContent(@NotNull PhpClass phpClass) { 171 | 172 | StringBuilder dummyMethods = new StringBuilder(); 173 | 174 | for (Method method : phpClass.getOwnMethods()) { 175 | String emptyMethod = getEmptyMethod(method); 176 | if(emptyMethod != null) { 177 | dummyMethods.append(emptyMethod); 178 | } 179 | } 180 | 181 | for (Field field : ContainerUtil.filter(phpClass.getOwnFields(), new MyDummyFieldCondition())) { 182 | String emptyMethod = getEmptyField(field); 183 | if(emptyMethod != null) { 184 | dummyMethods.append(emptyMethod); 185 | } 186 | } 187 | 188 | return dummyMethods.toString(); 189 | } 190 | 191 | @NotNull 192 | private static String buildTraitUses(@NotNull Map subClasses, @NotNull Map.Entry entryC) { 193 | Set filter = new HashSet(); 194 | 195 | // filter to find all foreign traits 196 | // we dont want to add self one 197 | for (Map.Entry stringStringEntry : subClasses.entrySet()) { 198 | if(entryC.getKey().equals(stringStringEntry.getKey())) { 199 | continue; 200 | } 201 | filter.add(stringStringEntry.getValue() + "Trait"); 202 | } 203 | 204 | if(filter.size() == 0) { 205 | return ""; 206 | } 207 | 208 | return String.format("\n use %s;\n", StringUtils.join(filter, ", ")); 209 | } 210 | 211 | public static void buildClassMetadataFile(final @NotNull Project project) { 212 | 213 | final String[] content1 = new String[] {null}; 214 | 215 | ApplicationManager.getApplication().runReadAction(() -> { 216 | final String content = OxidUtil.getPseudoClassOverwrites(project); 217 | if (content == null) { 218 | return; 219 | } 220 | content1[0] = content; 221 | }); 222 | 223 | if(content1[0] == null) { 224 | return; 225 | } 226 | 227 | new WriteCommandAction(project, null) { 228 | @Override 229 | protected void run(@NotNull Result result) throws Throwable { 230 | 231 | VirtualFile relativeFile = VfsUtil.findRelativeFile(project.getBaseDir(), PHPSTORM_OXID_META_PHP); 232 | 233 | if(relativeFile == null) { 234 | PsiDirectory directory = PsiManager.getInstance(getProject()).findDirectory(project.getBaseDir()); 235 | if(directory == null) { 236 | return; 237 | } 238 | 239 | relativeFile = directory.createFile(PHPSTORM_OXID_META_PHP).getVirtualFile(); 240 | } 241 | 242 | if(relativeFile == null) { 243 | return; 244 | } 245 | 246 | final PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(relativeFile); 247 | if(psiFile == null || psiFile.getFirstChild() == null) { 248 | return; 249 | } 250 | 251 | final PsiFile psiFileFromText = PhpPsiElementFactory.createPsiFileFromText(project, content1[0]); 252 | CodeStyleManager.getInstance(project).reformat(psiFileFromText); 253 | PsiElement firstChild = psiFileFromText.getFirstChild(); 254 | if(firstChild == null) { 255 | return; 256 | } 257 | 258 | psiFile.getFirstChild().replace(firstChild); 259 | } 260 | }.execute(); 261 | 262 | } 263 | 264 | public static Collection getOverloadAbleClasses(@NotNull Project project, @NotNull String contents) { 265 | 266 | Collection elements = new ArrayList(); 267 | 268 | // @TODO: is there a class filter on oxid, so we provide completion only for 269 | // "extends" classes 270 | PhpIndex phpIndex = PhpIndex.getInstance(project); 271 | for (String name : phpIndex.getAllClassNames(new CamelHumpMatcher(contents))) { 272 | for (PhpClass phpClass : phpIndex.getClassesByName(name)) { 273 | elements.add(new PhpClassLookupElement(phpClass, true, PhpClassReferenceInsertHandler.getInstance())); 274 | } 275 | } 276 | 277 | return elements; 278 | } 279 | 280 | private static class MyDummyFieldCondition implements Condition { 281 | @Override 282 | public boolean value(Field field) { 283 | return !field.isConstant() && (field.getModifier().getAccess().isPublic() || field.getModifier().getAccess().isProtected()); 284 | } 285 | } 286 | 287 | 288 | @Nullable 289 | private static String getEmptyMethod(@NotNull Method method) { 290 | 291 | StringBuilder s = new StringBuilder(); 292 | if(!(method.getAccess().isPublic() || method.getAccess().isProtected())) { 293 | return null; 294 | } 295 | 296 | String methodSignatureLine = method.getText(); 297 | int i = methodSignatureLine.indexOf("{"); 298 | if(i <= 0) { 299 | return null; 300 | } 301 | 302 | String docText = "/**\n*/"; 303 | 304 | PhpDocComment docComment = method.getDocComment(); 305 | if(docComment != null) { 306 | String docTextOrigin = docComment.getText(); 307 | if(StringUtils.isNotBlank(docTextOrigin)) { 308 | docText = docTextOrigin; 309 | } 310 | } 311 | 312 | PhpClass containingClass = method.getContainingClass(); 313 | if(containingClass == null) { 314 | return null; 315 | } 316 | 317 | String fqn = containingClass.getFQN(); 318 | if(fqn == null) { 319 | return null; 320 | } 321 | 322 | s.append(docText.replace("*/", String.format("* @see %s::%s\n*/", fqn, method.getName()))); 323 | s.append((methodSignatureLine.substring(0, i) + "{}").replaceAll("\\r\\n|\\r|\\n", " ").replaceAll(" +", " ")).append("\n"); 324 | 325 | return s.toString(); 326 | } 327 | 328 | @Nullable 329 | private static String getEmptyField(@NotNull Field field) { 330 | 331 | StringBuilder s = new StringBuilder(); 332 | if(!(field.getModifier().getAccess().isPublic() || field.getModifier().getAccess().isProtected())) { 333 | return null; 334 | } 335 | 336 | String modifierName = field.getModifier().toString(); 337 | 338 | PsiElement nameIdentifier = field.getNameIdentifier(); 339 | if(nameIdentifier == null) { 340 | return null; 341 | } 342 | 343 | String varName = nameIdentifier.getText(); 344 | if(!varName.startsWith("$")) { 345 | return null; 346 | } 347 | 348 | String docText = "/**\n*/"; 349 | 350 | PhpDocComment docComment = field.getDocComment(); 351 | if(docComment != null) { 352 | String text1 = docComment.getText(); 353 | if(StringUtils.isNotBlank(text1)) { 354 | docText = text1; 355 | } 356 | } 357 | 358 | PhpClass containingClass = field.getContainingClass(); 359 | if(containingClass == null) { 360 | return null; 361 | } 362 | 363 | String fqn = containingClass.getFQN(); 364 | if(fqn == null) { 365 | return null; 366 | } 367 | 368 | s.append(docText.replace("*/", String.format("* @see %s::%s\n*/", field.getContainingClass().getFQN(), field.getName()))); 369 | s.append(String.format("%s %s;", modifierName, varName)); 370 | 371 | return s.toString(); 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/PhpMetadataUtil.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import com.intellij.util.Processor; 5 | import com.jetbrains.php.lang.parser.PhpElementTypes; 6 | import com.jetbrains.php.lang.psi.elements.ArrayCreationExpression; 7 | import com.jetbrains.php.lang.psi.elements.ArrayHashElement; 8 | import com.jetbrains.php.lang.psi.elements.PhpPsiElement; 9 | import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; 10 | import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | /** 14 | * @author Daniel Espendiller 15 | */ 16 | public class PhpMetadataUtil { 17 | 18 | public static boolean isModuleKeyInFlatArray(@NotNull StringLiteralExpression psiElement, @NotNull String key) { 19 | PsiElement arrayKey = psiElement.getParent(); 20 | if(arrayKey != null && arrayKey.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) { 21 | PsiElement hashElement = arrayKey.getParent(); 22 | if(hashElement instanceof ArrayHashElement) { 23 | PsiElement arrayCreation = hashElement.getParent(); 24 | if(arrayCreation instanceof ArrayCreationExpression) { 25 | PsiElement arrayValue = arrayCreation.getParent(); 26 | if(arrayValue != null && arrayValue.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) { 27 | PsiElement hashArray = arrayValue.getParent(); 28 | if(hashArray instanceof ArrayHashElement) { 29 | PhpPsiElement keyString = ((ArrayHashElement) hashArray).getKey(); 30 | if(keyString instanceof StringLiteralExpression) { 31 | String contents = ((StringLiteralExpression) keyString).getContents(); 32 | if(key.equals(contents)) { 33 | return true; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | return false; 43 | } 44 | 45 | /** 46 | * Block keys, a really depth tree structure 47 | */ 48 | public static boolean isInTemplateWithKey(@NotNull StringLiteralExpression psiElement, @NotNull String key) { 49 | PsiElement arrayValue = psiElement.getParent(); 50 | if(arrayValue != null && arrayValue.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) { 51 | PsiElement hashElement = arrayValue.getParent(); 52 | if(hashElement instanceof ArrayHashElement) { 53 | PhpPsiElement keyString = ((ArrayHashElement) hashElement).getKey(); 54 | if(keyString instanceof StringLiteralExpression) { 55 | String contents = ((StringLiteralExpression) keyString).getContents(); 56 | if(key.equals(contents)) { 57 | 58 | PsiElement templateArrayValue = hashElement.getParent(); 59 | if(templateArrayValue instanceof ArrayCreationExpression) { 60 | PsiElement blockArrayValue = templateArrayValue.getParent(); 61 | if(blockArrayValue.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) { 62 | 63 | PsiElement arrayCrea = blockArrayValue.getParent(); 64 | if(arrayCrea instanceof ArrayCreationExpression) { 65 | 66 | PsiElement arrayValueBlock = arrayCrea.getParent(); 67 | if(arrayValueBlock != null && arrayValueBlock.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) { 68 | PsiElement blockHash = arrayValueBlock.getParent(); 69 | if(blockHash instanceof ArrayHashElement) { 70 | PhpPsiElement keyBlock = ((ArrayHashElement) blockHash).getKey(); 71 | if(keyBlock instanceof StringLiteralExpression) { 72 | String blockContents = ((StringLiteralExpression) keyBlock).getContents(); 73 | if("blocks".equals(blockContents)) { 74 | return true; 75 | } 76 | } 77 | } 78 | } 79 | 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | 91 | public static boolean isExtendKey(@NotNull StringLiteralExpression parent) { 92 | 93 | ArrayCreationExpression arrayCreation = PhpElementsUtil.getCompletableArrayCreationElement(parent); 94 | if(arrayCreation == null) { 95 | return false; 96 | } 97 | 98 | PsiElement arrayValue = arrayCreation.getParent(); 99 | if(arrayValue != null && arrayValue.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE) { 100 | PsiElement hashArray = arrayValue.getParent(); 101 | if(hashArray instanceof ArrayHashElement) { 102 | PhpPsiElement key = ((ArrayHashElement) hashArray).getKey(); 103 | if(key instanceof StringLiteralExpression && "extend".equals(((StringLiteralExpression) key).getContents())) { 104 | return true; 105 | } 106 | } 107 | } 108 | 109 | return false; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/SmartyBlockUtil.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import com.intellij.psi.PsiFile; 5 | import com.intellij.psi.search.PsiElementProcessor; 6 | import com.intellij.psi.util.PsiTreeUtil; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | /** 15 | * @author Daniel Espendiller 16 | */ 17 | public class SmartyBlockUtil { 18 | 19 | public static Collection getFileBlocks(@NotNull PsiFile psiFile) { 20 | 21 | final Collection blockNameSet = new ArrayList(); 22 | 23 | PsiTreeUtil.processElements(psiFile, new PsiElementProcessor() { 24 | @Override 25 | public boolean execute(@NotNull PsiElement element) { 26 | 27 | if (SmartyPattern.getBlockPattern().accepts(element)) { 28 | blockNameSet.add(new SmartyBlock(element, element.getText())); 29 | } 30 | 31 | return true; 32 | } 33 | }); 34 | 35 | return blockNameSet; 36 | } 37 | 38 | public static class SmartyBlock { 39 | 40 | final private PsiElement element; 41 | final private String name; 42 | 43 | public SmartyBlock(PsiElement element, String name) { 44 | this.element = element; 45 | this.name = name; 46 | } 47 | 48 | public PsiElement getElement() { 49 | return element; 50 | } 51 | 52 | public String getName() { 53 | return name; 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/SmartyPattern.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.patterns.PlatformPatterns; 4 | import com.intellij.patterns.PsiElementPattern; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.psi.PsiWhiteSpace; 7 | import com.jetbrains.smarty.lang.SmartyTokenTypes; 8 | import com.jetbrains.smarty.lang.psi.SmartyCompositeElementTypes; 9 | import com.jetbrains.smarty.lang.psi.SmartyTag; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | /** 13 | * @author Daniel Espendiller 14 | */ 15 | public class SmartyPattern { 16 | 17 | public static PsiElementPattern.Capture getFilePattern() { 18 | // getName dont work 19 | return PlatformPatterns.psiElement(SmartyTokenTypes.STRING_LITERAL).withParent( 20 | PlatformPatterns.psiElement(SmartyCompositeElementTypes.ATTRIBUTE_VALUE).withParent( 21 | PlatformPatterns.psiElement(SmartyCompositeElementTypes.ATTRIBUTE).withText(PlatformPatterns.string().contains("file=")) 22 | ) 23 | ); 24 | } 25 | 26 | public static PsiElementPattern.Capture getBlockPattern() { 27 | return PlatformPatterns.psiElement(SmartyTokenTypes.STRING_LITERAL).withParent( 28 | PlatformPatterns.psiElement(SmartyCompositeElementTypes.ATTRIBUTE_VALUE).withParent( 29 | PlatformPatterns.psiElement(SmartyCompositeElementTypes.ATTRIBUTE).withText(PlatformPatterns.string().contains("name=")).withParent( 30 | PlatformPatterns.psiElement(SmartyCompositeElementTypes.TAG).withText(PlatformPatterns.string().startsWith("{block")) 31 | ) 32 | ) 33 | ); 34 | } 35 | 36 | /** 37 | * Not all tags are known, we need some custom syntax check 38 | * 39 | * {oxmultilang ident="FOO"} 40 | * { oxmultilang ident="FOO"} 41 | */ 42 | public static PsiElementPattern.Capture getAttributeInsideTagPattern(@NotNull String attribute, @NotNull String tag) { 43 | return PlatformPatterns.psiElement(SmartyTokenTypes.STRING_LITERAL).afterLeafSkipping( 44 | PlatformPatterns.or( 45 | PlatformPatterns.psiElement(SmartyTokenTypes.DOUBLE_QUOTE), 46 | PlatformPatterns.psiElement(SmartyTokenTypes.SINGLE_QUOTE), 47 | PlatformPatterns.psiElement(SmartyTokenTypes.EQ), 48 | PlatformPatterns.psiElement(SmartyTokenTypes.WHITE_SPACE), 49 | PlatformPatterns.psiElement(PsiWhiteSpace.class) 50 | ), 51 | PlatformPatterns.psiElement(SmartyTokenTypes.IDENTIFIER).withText(attribute) 52 | ).withParent( 53 | PlatformPatterns.psiElement(SmartyTag.class).withText( 54 | PlatformPatterns.string().matches("\\{\\s*" + tag + ".*") 55 | ) 56 | ); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/TemplateUtil.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.codeInsight.lookup.LookupElement; 4 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.vfs.VfsUtil; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.psi.PsiManager; 11 | import com.intellij.psi.PsiRecursiveElementWalkingVisitor; 12 | import com.intellij.psi.search.FileTypeIndex; 13 | import com.intellij.psi.search.FilenameIndex; 14 | import com.intellij.psi.search.GlobalSearchScope; 15 | import com.intellij.util.Processor; 16 | import com.intellij.util.indexing.FileBasedIndex; 17 | import com.intellij.util.indexing.FileBasedIndexImpl; 18 | import com.jetbrains.smarty.SmartyFileType; 19 | import de.espend.idea.oxid.OxidPluginIcons; 20 | import de.espend.idea.oxid.stub.OxidContentIdentIndexer; 21 | import fr.adrienbrault.idea.symfony2plugin.stubs.SymfonyProcessors; 22 | import org.apache.commons.lang.StringUtils; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.util.*; 26 | 27 | /** 28 | * @author Daniel Espendiller 29 | */ 30 | public class TemplateUtil { 31 | 32 | public static void collectFiles(@NotNull Project project, @NotNull final SmartyTemplateVisitor visitor) { 33 | 34 | 35 | for(VirtualFile virtualFile : FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, SmartyFileType.INSTANCE, GlobalSearchScope.allScope(project))) { 36 | 37 | // try to get /templates/frontend/... 38 | String path = virtualFile.toString(); 39 | int i = path.lastIndexOf("/application/views/"); 40 | if(i > 0) { 41 | String frontendName = path.substring(i + "/application/views/".length()); 42 | attachTemplates(virtualFile, frontendName, visitor); 43 | } 44 | } 45 | 46 | for (PsiFile psiFile : FilenameIndex.getFilesByName(project, "metadata.php", GlobalSearchScope.allScope(project))) { 47 | for (Map.Entry entry : MetadataUtil.getMetadataKeyMap(psiFile, "templates").entrySet()) { 48 | 49 | VirtualFile parent = psiFile.getVirtualFile().getParent(); 50 | if(parent != null) { 51 | VirtualFile moduleFolder = parent.getParent(); 52 | if(moduleFolder != null) { 53 | VirtualFile mainModule = moduleFolder.getParent(); 54 | if(mainModule != null) { 55 | String[] split = entry.getValue().split("/"); 56 | VirtualFile relativeFile = VfsUtil.findRelativeFile(mainModule, split); 57 | if(relativeFile != null) { 58 | visitor.visitFile(relativeFile, entry.getKey()); 59 | } 60 | } 61 | } 62 | } 63 | 64 | } 65 | } 66 | 67 | } 68 | private static void attachTemplates(VirtualFile virtualFile, String frontendName, SmartyTemplateVisitor smartyTemplateVisitor) { 69 | 70 | String[] pathSplits = StringUtils.split(frontendName, "/"); 71 | if(pathSplits.length <= 2 || pathSplits[0].equals("admin") || !pathSplits[1].equals("tpl") ) { 72 | return; 73 | } 74 | 75 | int n = pathSplits.length - 2; 76 | String[] newArray = new String[n]; 77 | System.arraycopy(pathSplits, 2, newArray, 0, n); 78 | 79 | String fileName = StringUtils.join(newArray, "/"); 80 | smartyTemplateVisitor.visitFile(virtualFile, fileName); 81 | } 82 | 83 | @NotNull 84 | public static Collection getBlocksTemplateName(@NotNull Project project, final @NotNull String templateName) { 85 | 86 | final Collection blocks = new ArrayList(); 87 | 88 | for (VirtualFile virtualFile : getFilesByTemplateName(project, templateName)) { 89 | PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); 90 | if(file != null) { 91 | blocks.addAll(SmartyBlockUtil.getFileBlocks(file)); 92 | } 93 | } 94 | 95 | return blocks; 96 | } 97 | 98 | public static Collection getFilesByTemplateName(@NotNull Project project, final @NotNull String templateName) { 99 | 100 | final Collection files = new ArrayList(); 101 | 102 | TemplateUtil.collectFiles(project, new TemplateUtil.SmartyTemplateVisitor() { 103 | @Override 104 | public void visitFile(VirtualFile virtualFile, String fileName) { 105 | if(fileName.equalsIgnoreCase(templateName)) { 106 | files.add(virtualFile); 107 | } 108 | } 109 | }); 110 | 111 | return files; 112 | } 113 | 114 | public interface SmartyTemplateVisitor { 115 | public void visitFile(VirtualFile virtualFile, String fileName); 116 | } 117 | 118 | @NotNull 119 | public static Set getTemplateNames(@NotNull Project project, final @NotNull VirtualFile virtualFile) { 120 | 121 | final Set templates = new HashSet(); 122 | 123 | TemplateUtil.collectFiles(project, new TemplateUtil.SmartyTemplateVisitor() { 124 | @Override 125 | public void visitFile(VirtualFile templateFile, String fileName) { 126 | if(templateFile.equals(virtualFile)) { 127 | templates.add(fileName); 128 | } 129 | 130 | } 131 | }); 132 | 133 | return templates; 134 | } 135 | 136 | public static Collection getBlockFileLookupElements(@NotNull Project project, String... templateNames) { 137 | 138 | Set blocks = new HashSet(); 139 | 140 | for (String template : new HashSet(Arrays.asList(templateNames))) { 141 | for (SmartyBlockUtil.SmartyBlock smartyBlock : TemplateUtil.getBlocksTemplateName(project, template)) { 142 | if(!blocks.contains(smartyBlock.getName())) { 143 | blocks.add(smartyBlock.getName()); 144 | } 145 | } 146 | } 147 | 148 | Collection lookupElements = new ArrayList(); 149 | for (String block : blocks) { 150 | lookupElements.add(LookupElementBuilder.create(block).withIcon(OxidPluginIcons.OXID).withTypeText("block", true)); 151 | } 152 | 153 | return lookupElements; 154 | } 155 | 156 | public static Set getContentIdents(@NotNull Project project) { 157 | SymfonyProcessors.CollectProjectUniqueKeys processor = new SymfonyProcessors.CollectProjectUniqueKeys(project, OxidContentIdentIndexer.KEY); 158 | FileBasedIndex.getInstance().processAllKeys(OxidContentIdentIndexer.KEY, processor, project); 159 | return processor.getResult(); 160 | } 161 | 162 | public static Set getContentIdentsTargets(final @NotNull Project project, final @NotNull VirtualFile currentFile, final @NotNull String ident) { 163 | 164 | final Set psiElements = new HashSet(); 165 | 166 | FileBasedIndexImpl.getInstance().getFilesWithKey(OxidContentIdentIndexer.KEY, new HashSet(Arrays.asList(ident)), new Processor() { 167 | @Override 168 | public boolean process(VirtualFile virtualFile) { 169 | 170 | if (currentFile.equals(virtualFile)) { 171 | return true; 172 | } 173 | 174 | PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); 175 | if (psiFile == null) { 176 | return true; 177 | } 178 | 179 | psiFile.acceptChildren(new PsiRecursiveElementWalkingVisitor() { 180 | @Override 181 | public void visitElement(PsiElement element) { 182 | 183 | if(SmartyPattern.getAttributeInsideTagPattern("ident", "oxcontent").accepts(element)) { 184 | String content = element.getText(); 185 | if(StringUtils.isNotBlank(content) && content.equalsIgnoreCase(ident)) { 186 | psiElements.add(element); 187 | } 188 | } 189 | 190 | super.visitElement(element); 191 | } 192 | 193 | }); 194 | 195 | 196 | return true; 197 | } 198 | }, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), SmartyFileType.INSTANCE)); 199 | 200 | return psiElements; 201 | } 202 | 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/de/espend/idea/oxid/utils/TranslationUtil.java: -------------------------------------------------------------------------------- 1 | package de.espend.idea.oxid.utils; 2 | 3 | import com.intellij.codeInsight.lookup.LookupElement; 4 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiFile; 9 | import com.intellij.psi.PsiManager; 10 | import com.intellij.psi.search.FilenameIndex; 11 | import com.intellij.psi.search.GlobalSearchScope; 12 | import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | 20 | /** 21 | * @author Daniel Espendiller 22 | */ 23 | public class TranslationUtil { 24 | 25 | public static Collection getTranslationLookupElements(@NotNull Project project) { 26 | 27 | final Set keys = new HashSet(); 28 | 29 | for (VirtualFile virtualFile : FilenameIndex.getAllFilesByExt(project, "php", GlobalSearchScope.allScope(project))) { 30 | 31 | if (!isTranslationFile(virtualFile)) { 32 | continue; 33 | } 34 | 35 | PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); 36 | if (file != null) { 37 | MetadataUtil.visitTranslationKey(file, new MetadataUtil.TranslationKeyVisitor() { 38 | @Override 39 | public void visit(@NotNull String name, @NotNull PsiElement value) { 40 | keys.add(name); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | Collection elements = new ArrayList(); 47 | 48 | for (String setting : keys) { 49 | elements.add(LookupElementBuilder.create(setting).withIcon(Symfony2Icons.TRANSLATION)); 50 | } 51 | 52 | return elements; 53 | } 54 | 55 | public static Collection getTranslationTargets(@NotNull Project project, final @NotNull String translationName) { 56 | 57 | final Collection targets = new ArrayList(); 58 | 59 | for (VirtualFile virtualFile : FilenameIndex.getAllFilesByExt(project, "php", GlobalSearchScope.allScope(project))) { 60 | 61 | if (!isTranslationFile(virtualFile)) { 62 | continue; 63 | } 64 | 65 | PsiFile file = PsiManager.getInstance(project).findFile(virtualFile); 66 | if (file != null) { 67 | MetadataUtil.visitTranslationKey(file, new MetadataUtil.TranslationKeyVisitor() { 68 | @Override 69 | public void visit(@NotNull String name, @NotNull PsiElement value) { 70 | if(name.equalsIgnoreCase(translationName)) { 71 | targets.add(value); 72 | } 73 | 74 | } 75 | }); 76 | } 77 | } 78 | 79 | return targets; 80 | } 81 | 82 | private static boolean isTranslationFile(@NotNull VirtualFile virtualFile) { 83 | return virtualFile.getName().endsWith("_lang.php") || virtualFile.getName().equalsIgnoreCase("lang.php"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/inspectionDescriptions/OxidMetadataFile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Oxid metadata file not found 4 | 5 | 6 | --------------------------------------------------------------------------------