├── Tests ├── Fixtures │ ├── Templates │ │ ├── CodeTemplate.phpt │ │ └── AnalysisFixture.html │ └── Classes │ │ ├── DummyPersistenceManager.php │ │ ├── DummyPersistenceBackend.php │ │ └── DummyConfigurationManager.php ├── Unit │ ├── Parser │ │ └── ExposedTemplateParserTest.php │ ├── Controller │ │ └── BackendControllerTest.php │ ├── Command │ │ └── BuilderCommandControllerTest.php │ ├── Analysis │ │ ├── AbstractMessageTest.php │ │ ├── MetricTest.php │ │ └── Fluid │ │ │ └── NodeCounterTest.php │ └── CodeGeneration │ │ ├── CodeTemplateTest.php │ │ └── Extension │ │ └── ExtensionGeneratorTest.php └── bootstrap.php ├── ext_icon.gif ├── .gitignore ├── Resources ├── Public │ ├── Icons │ │ ├── builder.gif │ │ ├── module_builder.png │ │ └── Example.svg │ ├── Stylesheet │ │ ├── plotter.css │ │ ├── analysis.css │ │ └── jqplot.min.css │ └── Javascript │ │ ├── plotter.js │ │ ├── jqplot.canvasAxisTickRenderer.min.js │ │ ├── jqplot.pointLabels.min.js │ │ ├── jqplot.categoryAxisRenderer.min.js │ │ └── jqplot.barRenderer.min.js └── Private │ ├── CodeTemplates │ ├── Fluid │ │ ├── Layout.phpt │ │ ├── Form.phpt │ │ ├── BackendLayout.phpt │ │ ├── Content.phpt │ │ └── Page.phpt │ ├── Extension │ │ ├── ext_localconf.phpt │ │ ├── TypoScript │ │ │ ├── constants.phpt │ │ │ └── setup.phpt │ │ ├── Language │ │ │ └── locallang.xlf.phpt │ │ └── ext_emconf.phpt │ └── Controller │ │ └── Controller.phpt │ ├── Templates │ └── Backend │ │ ├── Build.html │ │ ├── Index.html │ │ ├── BuildForm.html │ │ └── Syntax.html │ ├── Layouts │ └── Backend.html │ ├── Language │ ├── Module │ │ └── locallang_csh.xlf │ └── locallang.xlf │ └── Partials │ └── Receipt.html ├── Classes ├── Result │ ├── ParserResultInterface.php │ ├── FluidParserResult.php │ └── ParserResult.php ├── Analysis │ ├── OkMessage.php │ ├── NoticeMessage.php │ ├── WarningMessage.php │ ├── Fluid │ │ ├── Message │ │ │ └── UncompilableMessage.php │ │ └── TemplateAnalyzer.php │ ├── MessageInterface.php │ ├── AbstractMessage.php │ └── Metric.php ├── CodeGeneration │ ├── CodeGeneratorInterface.php │ ├── ClassGeneratorInterface.php │ ├── CodeTemplate.php │ ├── AbstractCodeGenerator.php │ ├── AbstractClassGenerator.php │ └── Extension │ │ └── ExtensionGenerator.php ├── Service │ ├── ClassAnalysisService.php │ ├── SyntaxService.php │ ├── ExtensionService.php │ └── CommandService.php ├── Utility │ ├── ExtensionUtility.php │ └── GlobUtility.php ├── Command │ ├── PhpSyntaxCommand.php │ ├── FluidSyntaxCommand.php │ ├── ListCommand.php │ ├── BuildCommand.php │ └── BuilderCommandController.php ├── Parser │ ├── ExposedTemplateCompiler.php │ └── ExposedTemplateParser.php └── Controller │ └── BackendController.php ├── ext_localconf.php ├── CONTRIBUTING.md ├── .editorconfig ├── .travis.yml ├── phpunit.xml.dist ├── composer.json ├── LICENSE.md ├── Configuration └── Services.yaml ├── ext_emconf.php ├── ext_tables.php └── README.md /Tests/Fixtures/Templates/CodeTemplate.phpt: -------------------------------------------------------------------------------- 1 | content: ###foo### 2 | -------------------------------------------------------------------------------- /ext_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluidTYPO3/builder/HEAD/ext_icon.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea 3 | build 4 | composer.lock 5 | /index.php 6 | /typo3 7 | /typo3_src 8 | /typo3conf 9 | -------------------------------------------------------------------------------- /Resources/Public/Icons/builder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluidTYPO3/builder/HEAD/Resources/Public/Icons/builder.gif -------------------------------------------------------------------------------- /Resources/Private/CodeTemplates/Fluid/Layout.phpt: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Resources/Public/Icons/module_builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluidTYPO3/builder/HEAD/Resources/Public/Icons/module_builder.png -------------------------------------------------------------------------------- /Classes/Result/ParserResultInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | I am a template file. I contain one section: 4 | 5 | 6 | I am a section, I am a condition that is 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/Private/CodeTemplates/Extension/TypoScript/constants.phpt: -------------------------------------------------------------------------------- 1 | plugin.###signature###.view { 2 | templateRootPaths.0 = EXT:###extension###/Resources/Private/Templates/ 3 | partialRootPaths.0 = EXT:###extension###/Resources/Private/Partials/ 4 | layoutRootPaths.0 = EXT:###extension###/Resources/Private/Layouts/ 5 | } 6 | -------------------------------------------------------------------------------- /ext_localconf.php: -------------------------------------------------------------------------------- 1 | https://fluidtypo3.org/documentation/contributing/contribution-guide.html 9 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Backend/Build.html: -------------------------------------------------------------------------------- 1 | {namespace v=FluidTYPO3\Vhs\ViewHelpers} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {csh -> f:format.raw()} 11 | 12 | 13 | 14 |

{f:translate(key: 'header.builder')}

15 |

{f:translate(key: 'header.built')}

16 | 17 |
18 | -------------------------------------------------------------------------------- /Classes/Analysis/OkMessage.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

I am a standard template file!

11 |

12 | My template file is EXT:###extension###/Resources/Private/###placement###. 13 |

14 |
15 | -------------------------------------------------------------------------------- /Resources/Private/Layouts/Backend.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | Syntax Check 5 | Generate Provider Extension 6 |
7 |

 

8 | 9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /Classes/Analysis/Fluid/Message/UncompilableMessage.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Builder 8 | 9 | 10 | Builder 11 | 12 | 13 | Builder 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Classes/CodeGeneration/CodeGeneratorInterface.php: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | Tests 18 | 19 | 20 | 21 | 22 | Classes 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluidtypo3/builder", 3 | "description": "The builder package from FluidTYPO3", 4 | "type": "typo3-cms-extension", 5 | "replace": { 6 | "builder": "self.version", 7 | "typo3-ter/builder": "self.version" 8 | }, 9 | "require": { 10 | "php": ">=7.2.0", 11 | "fluidtypo3/vhs": ">=2.2.0||dev-development", 12 | "typo3/cms-core": "^8.7||^9||^10||dev-master", 13 | "typo3/cms-backend": "^8.7||^9||^10||dev-master", 14 | "typo3/cms-fluid": "^8.7||^9||^10||dev-master" 15 | }, 16 | "require-dev": { 17 | "fluidtypo3/development": "^4.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "FluidTYPO3\\Builder\\": "Classes/" 22 | } 23 | }, 24 | "non-feature-branches": ["development", "staging"] 25 | } 26 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### Copyright notice 2 | 3 | > (c) 2014 Claus Due 4 | > All rights reserved 5 | 6 | This repository is part of the TYPO3 project. The TYPO3 project is 7 | free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | The GNU General Public License can be found at 13 | http://www.gnu.org/copyleft/gpl.html. 14 | 15 | This repository is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | This copyright notice MUST APPEAR in all copies of the repository! 21 | -------------------------------------------------------------------------------- /Classes/Analysis/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /Configuration/Services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: false 6 | 7 | FluidTYPO3\Builder\: 8 | resource: '../Classes/*' 9 | 10 | FluidTYPO3\Builder\Command\BuildCommand: 11 | tags: 12 | - name: 'console.command' 13 | command: 'builder:build' 14 | schedulable: false 15 | FluidTYPO3\Builder\Command\FluidSyntaxCommand: 16 | tags: 17 | - name: 'console.command' 18 | command: 'builder:fluidsyntax' 19 | schedulable: false 20 | FluidTYPO3\Builder\Command\PhpSyntaxCommand: 21 | tags: 22 | - name: 'console.command' 23 | command: 'builder:phpsyntax' 24 | schedulable: false 25 | FluidTYPO3\Builder\Command\ListCommand: 26 | tags: 27 | - name: 'console.command' 28 | command: 'builder:list' 29 | schedulable: false 30 | -------------------------------------------------------------------------------- /Resources/Private/CodeTemplates/Fluid/Content.phpt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Your Input:
13 | {settings.helloWorld} 14 |
15 | 16 | 17 |

I am a content element!

18 |

19 | My template file is EXT:###extension###/Resources/Private/###placement###. 20 |

21 |

Frontend output

22 |

23 | {settings.helloWorld} 24 |

25 |
26 | -------------------------------------------------------------------------------- /Classes/CodeGeneration/ClassGeneratorInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Example page 8 | 9 | 10 | Description can be entered in LLL file locallang.xlf of extension test 11 | 12 | 13 | Example element 14 | 15 | 16 | Small example element 17 | 18 | 19 | Example input 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ext_emconf.php: -------------------------------------------------------------------------------- 1 | 'Builder: FluidTYPO3 Development Support', 4 | 'description' => 'Various development supports for building and working with Fluid templates and Extbase extensions', 5 | 'category' => 'misc', 6 | 'shy' => 0, 7 | 'version' => '1.2.0', 8 | 'dependencies' => '', 9 | 'conflicts' => '', 10 | 'priority' => '', 11 | 'loadOrder' => '', 12 | 'module' => '', 13 | 'state' => 'beta', 14 | 'uploadfolder' => 0, 15 | 'createDirs' => '', 16 | 'modify_tables' => '', 17 | 'clearCacheOnLoad' => 1, 18 | 'lockType' => '', 19 | 'author' => 'FluidTYPO3 Team', 20 | 'author_email' => 'claus@namelesscoder.net', 21 | 'author_company' => '', 22 | 'constraints' => array ( 23 | 'depends' => array ( 24 | 'php' => '7.0.0-7.4.99', 25 | 'typo3' => '8.7.0-10.4.99', 26 | 'vhs' => '2.2.0-6.99.99', 27 | ), 28 | 'conflicts' => array ( 29 | ), 30 | 'suggests' => array ( 31 | ), 32 | ), 33 | '_md5_values_when_last_written' => '', 34 | 'suggests' => array ( 35 | ), 36 | ); 37 | -------------------------------------------------------------------------------- /ext_tables.php: -------------------------------------------------------------------------------- 1 | 'index,syntax,build,buildForm', 17 | ], 18 | [ 19 | 'access' => 'user,group', 20 | 'icon' => 'EXT:builder/Resources/Public/Icons/module_builder.png', 21 | 'labels' => 'LLL:EXT:builder/Resources/Private/Language/locallang.xlf' 22 | ] 23 | ); 24 | 25 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addLLrefForTCAdescr( 26 | 'builder', 27 | 'EXT:builder/Resources/Private/Language/locallang.xlf' 28 | ); 29 | } 30 | 31 | })(); 32 | } 33 | -------------------------------------------------------------------------------- /Resources/Private/CodeTemplates/Fluid/Page.phpt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

I am a page template!

18 |

19 | My template file is EXT:###extension###/Resources/Private/###placement###. 20 |

21 |
22 |

Content main

23 | 24 |
25 |
26 |

Content right

27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /Classes/Service/ClassAnalysisService.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 22 | } 23 | 24 | /** 25 | * @param mixed $classOrInstance 26 | * @param string $methodName 27 | * @return boolean 28 | */ 29 | public function assertClassMethodHasRequiredArguments($classOrInstance, $methodName) 30 | { 31 | if (false === is_object($classOrInstance)) { 32 | $classOrInstance = $this->objectManager->get($classOrInstance); 33 | } 34 | $reflection = new \ReflectionClass($classOrInstance); 35 | $methodReflection = $reflection->getMethod($methodName); 36 | $arguments = $methodReflection->getParameters(); 37 | foreach ($arguments as $argumentReflection) { 38 | if (false === $argumentReflection->isOptional()) { 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Resources/Private/CodeTemplates/Extension/ext_emconf.phpt: -------------------------------------------------------------------------------- 1 | '###title###', 15 | 'description' => '###description###', 16 | 'category' => 'misc', 17 | 'shy' => 0, 18 | 'version' => '0.0.1', 19 | 'dependencies' => 'typo3,flux###dependenciesCsv###', 20 | 'conflicts' => '', 21 | 'priority' => '', 22 | 'loadOrder' => '', 23 | 'module' => '', 24 | 'state' => 'experimental', 25 | 'uploadfolder' => 0, 26 | 'createDirs' => '', 27 | 'modify_tables' => '', 28 | 'clearCacheOnLoad' => 1, 29 | 'lockType' => '', 30 | 'author' => '###author###', 31 | 'author_email' => '###email###', 32 | 'author_company' => '###company###', 33 | 'CGLcompliance' => '', 34 | 'CGLcompliance_note' => '', 35 | 'constraints' => array( 36 | 'depends' => array( 37 | 'typo3' => '###coreMinor###-###coreMajor###', 38 | 'flux' => '', 39 | ###dependenciesArray### 40 | ), 41 | 'conflicts' => array( 42 | ), 43 | 'suggests' => array( 44 | ), 45 | ), 46 | '_md5_values_when_last_written' => 'a:0:{}', 47 | 'suggests' => array( 48 | ), 49 | ); 50 | -------------------------------------------------------------------------------- /Tests/Fixtures/Classes/DummyConfigurationManager.php: -------------------------------------------------------------------------------- 1 | getMock('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer', array('RECORDS')); 21 | return $renderer; 22 | } 23 | 24 | /** 25 | * @param string $type 26 | * @param string $extensionName 27 | * @param string $pluginName 28 | * @return array 29 | */ 30 | public function getConfiguration($type, $extensionName = null, $pluginName = null) 31 | { 32 | return array( 33 | 'plugin.' => array( 34 | 'tx_builder.' => array( 35 | 'view.' => array(), 36 | 'settings.' => array(), 37 | ) 38 | ) 39 | ); 40 | } 41 | 42 | /** 43 | * @param string $featureName 44 | * @return boolean 45 | */ 46 | public function isFeatureEnabled($featureName) 47 | { 48 | true; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/Analysis/AbstractMessage.php: -------------------------------------------------------------------------------- 1 | message = $message; 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getMessage() 41 | { 42 | return $this->message; 43 | } 44 | 45 | /** 46 | * @param mixed $payload 47 | * @return void 48 | */ 49 | public function setPayload($payload) 50 | { 51 | $this->payload = $payload; 52 | } 53 | 54 | /** 55 | * @return mixed 56 | */ 57 | public function getPayload() 58 | { 59 | return $this->payload; 60 | } 61 | 62 | /** 63 | * @param integer $severity 64 | * @return void 65 | */ 66 | public function setSeverity($severity) 67 | { 68 | $this->severity = $severity; 69 | } 70 | 71 | /** 72 | * @return integer 73 | */ 74 | public function getSeverity() 75 | { 76 | return $this->severity; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Classes/Utility/ExtensionUtility.php: -------------------------------------------------------------------------------- 1 | layoutName = $layoutName; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getLayoutName() 35 | { 36 | return $this->layoutName; 37 | } 38 | 39 | /** 40 | * @param array $namespaces 41 | * @return void 42 | */ 43 | public function setNamespaces($namespaces) 44 | { 45 | $this->namespaces = $namespaces; 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | public function getNamespaces() 52 | { 53 | return $this->namespaces; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getNamespacesFlattened() 60 | { 61 | $flat = []; 62 | foreach ($this->namespaces as $namespaceAlias => $classPath) { 63 | array_push($flat, $namespaceAlias . '=[' . implode(', ', $classPath) . ']'); 64 | } 65 | return implode(', ', $flat); 66 | } 67 | 68 | /** 69 | * @param boolean $compilable 70 | */ 71 | public function setCompilable($compilable) 72 | { 73 | $this->compilable = $compilable; 74 | } 75 | 76 | /** 77 | * @return boolean 78 | */ 79 | public function getCompilable() 80 | { 81 | return $this->compilable; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Backend/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {csh -> f:format.raw()} 9 | 10 | 11 | 12 | 13 |
14 | {f:translate(key: 'header.validators')} 15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 | 31 | 35 |
36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 |
    45 | 46 |
  • 47 | 50 |
  • 51 |
    52 |
53 |
54 | -------------------------------------------------------------------------------- /Classes/Utility/GlobUtility.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Builder\Parser\ExposedTemplateParser; 28 | use FluidTYPO3\Development\AbstractTestCase; 29 | 30 | /** 31 | * Class ExposedTemplateParserTest 32 | */ 33 | class ExposedTemplateParserTest extends AbstractTestCase 34 | { 35 | 36 | /** 37 | * @param string $input 38 | * @test 39 | * @dataProvider getInvalidTemplateStringTestValues 40 | */ 41 | public function testParseThrowsExceptionOnInvalidTemplateString($input) 42 | { 43 | $parser = new ExposedTemplateParser(); 44 | $this->setExpectedException('TYPO3Fluid\\Fluid\\Core\\Parser\\Exception'); 45 | $parser->parse($input, 'xyz'); 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | public function getInvalidTemplateStringTestValues() 52 | { 53 | return array( 54 | array(1), 55 | array(new \stdClass()), 56 | array(null), 57 | array(false) 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/Unit/Controller/BackendControllerTest.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Development\AbstractTestCase; 28 | use TYPO3\CMS\Core\Utility\GeneralUtility; 29 | 30 | /** 31 | * Class BackendControllerTest 32 | */ 33 | class BackendControllerTest extends AbstractTestCase 34 | { 35 | 36 | /** 37 | * @test 38 | */ 39 | public function injectsExpectedProperties() 40 | { 41 | $class = $this->getInstanceClassName(); 42 | $instance = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager')->get($class); 43 | $this->assertAttributeInstanceOf('FluidTYPO3\\Builder\\Service\\SyntaxService', 'syntaxService', $instance); 44 | $this->assertAttributeInstanceOf('FluidTYPO3\\Builder\\Service\\ExtensionService', 'extensionService', $instance); 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | protected function getInstanceClassName() 51 | { 52 | return substr(str_replace('Tests\\Unit\\', '', get_class($this)), 0, -4); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Resources/Public/Javascript/plotter.js: -------------------------------------------------------------------------------- 1 | 2 | jQuery(document).ready(function($) { 3 | var metrics = $.parseJSON($('.storage.storage-profile').html()); 4 | var ticks = []; 5 | var series = []; 6 | var values = []; 7 | var legendItemHeight = 18; 8 | for (var filename in metrics) { 9 | series.push({ 10 | label: filename, 11 | neighborThreshold: 0 12 | }); 13 | var dataset = []; 14 | for (var metricName in metrics[filename]) { 15 | var value = metrics[filename][metricName]; 16 | if (-1 == ticks.indexOf(metricName)) { 17 | ticks.push(metricName); 18 | }; 19 | maxHeight = Math.max(maxHeight, value); 20 | dataset.push([metricName, value]); 21 | }; 22 | values.push(dataset); 23 | }; 24 | var barMargin = 0; 25 | var barWidth = Math.round((800 / ticks.length) / series.length); 26 | var maxHeight =+ (300 + (series.length * legendItemHeight)); 27 | var scale = ticks.length * values.length; 28 | var plotter = $('.graph').jqplot('graph', values, { 29 | width: ((barWidth + (barMargin * 2)) * scale) + 35, 30 | height: maxHeight, 31 | seriesColors: ['#002b36', '#586e75', '#839496', '#b58900', '#cb4b16', '#dc322f', '#d33682', '#6c71c4', '#268bd2', '#2aa198', '#859900', '#073642', '#657b83', '#93a1a1'], 32 | seriesDefaults:{ 33 | renderer: $.jqplot.BarRenderer, 34 | rendererOptions: { 35 | //barDirection: 'horizontal', 36 | shadow: false, 37 | barWidth: barWidth, 38 | barMargin: barMargin, 39 | barPadding: 0 40 | }, 41 | trendline: { 42 | show: true 43 | } 44 | }, 45 | legend: { 46 | show: true, 47 | placement: 'insideGrid', 48 | location: 'ne' 49 | }, 50 | series: series, 51 | grid: { 52 | background: '#FAFAFA' 53 | }, 54 | cursor:{ 55 | show: true, 56 | zoom: true, 57 | showTooltip: false, 58 | followMouse: true 59 | }, 60 | axes: { 61 | yaxis: { 62 | padMin: 0 63 | }, 64 | xaxis: { 65 | renderer: $.jqplot.CategoryAxisRenderer, 66 | tickRenderer: $.jqplot.CanvasAxisTickRenderer , 67 | tickOptions: { 68 | angle: -90, 69 | fontSize: '12pt' 70 | } 71 | } 72 | } 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /Tests/Unit/Command/BuilderCommandControllerTest.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Development\AbstractTestCase; 28 | use TYPO3\CMS\Core\Utility\GeneralUtility; 29 | 30 | /** 31 | * Class BuilderCommandControllerTest 32 | */ 33 | class BuilderCommandControllerTest extends AbstractTestCase 34 | { 35 | 36 | /** 37 | * @test 38 | */ 39 | public function injectsExpectedProperties() 40 | { 41 | $class = $this->getInstanceClassName(); 42 | $instance = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager')->get($class); 43 | $this->assertAttributeInstanceOf('FluidTYPO3\\Builder\\Service\\SyntaxService', 'syntaxService', $instance); 44 | $this->assertAttributeInstanceOf('FluidTYPO3\\Builder\\Service\\ExtensionService', 'extensionService', $instance); 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | protected function getInstanceClassName() 51 | { 52 | return substr(str_replace('Tests\\Unit\\', '', get_class($this)), 0, -4); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/Unit/Analysis/AbstractMessageTest.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Development\AbstractTestCase; 28 | 29 | /** 30 | * Class AbstractMessageTest 31 | */ 32 | class AbstractMessageTest extends AbstractTestCase 33 | { 34 | 35 | /** 36 | * @param string $name 37 | * @param mixed $value 38 | * @test 39 | * @dataProvider getGetterAndSetterTestValues 40 | */ 41 | public function testGetterAndSetter($name, $value) 42 | { 43 | $message = $this->getMockForAbstractClass('FluidTYPO3\\Builder\\Analysis\\AbstractMessage'); 44 | $setter = 'set' . ucfirst($name); 45 | $getter = 'get' . ucfirst($name); 46 | $message->$setter($value); 47 | $this->assertEquals($value, $message->$getter()); 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public function getGetterAndSetterTestValues() 54 | { 55 | return array( 56 | array('message', 'I am a message'), 57 | array('severity', 1), 58 | array('payload', array('foo' => 'bar')) 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Classes/Command/PhpSyntaxCommand.php: -------------------------------------------------------------------------------- 1 | commandService = $commandService ?? GeneralUtility::makeInstance(ObjectManager::class)->get(CommandService::class); 23 | } 24 | 25 | protected function configure() 26 | { 27 | $this->setDescription('Builder: check PHP syntax') 28 | ->setHelp('Checks the syntax validity of PHP files (in provided extension, path, or path in extension') 29 | ->addOption( 30 | 'extension', 31 | 'e', 32 | InputOption::VALUE_OPTIONAL, 33 | 'Extension key to check, if not specified will check all extensions containing PHP files' 34 | )->addOption( 35 | 'path', 36 | 'p', 37 | InputOption::VALUE_OPTIONAL, 38 | 'File or folder path (if extensionKey is included, path is relative to this extension)' 39 | ); 40 | } 41 | 42 | /** 43 | * Execute scheduler tasks 44 | * 45 | * @param InputInterface $input 46 | * @param OutputInterface $output 47 | * @return int 48 | */ 49 | public function execute(InputInterface $input, OutputInterface $output): int 50 | { 51 | $extension = $input->getOption('extension'); 52 | $path = $input->getOption('path'); 53 | $verbose = (bool) $input->getOption('verbose'); 54 | $this->commandService->setOutput($output); 55 | return $this->commandService->checkPhpSyntax($extension, $path, $verbose); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Classes/Command/FluidSyntaxCommand.php: -------------------------------------------------------------------------------- 1 | commandService = $commandService ?? GeneralUtility::makeInstance(ObjectManager::class)->get(CommandService::class); 23 | } 24 | 25 | protected function configure() 26 | { 27 | $this->setDescription('Builder: check Fluid syntax') 28 | ->setHelp('Checks the syntax validity of Fluid files (in provided extension, path, or path in extension') 29 | ->addOption( 30 | 'extension', 31 | 'e', 32 | InputOption::VALUE_OPTIONAL, 33 | 'Extension key to check, if not specified will check all extensions containing Fluid templates' 34 | )->addOption( 35 | 'path', 36 | 'p', 37 | InputOption::VALUE_OPTIONAL, 38 | 'File or folder path (if extensionKey is included, path is relative to this extension)' 39 | )->addOption( 40 | 'extensions', 41 | 'x', 42 | InputOption::VALUE_OPTIONAL, 43 | 'If provided, this CSV list of file extensions are considered Fluid templates', 44 | 'html,xml,txt' 45 | ); 46 | } 47 | 48 | protected function execute(InputInterface $input, OutputInterface $output): int 49 | { 50 | $extension = $input->getOption('extension'); 51 | $path = $input->getOption('path'); 52 | $extensions = $input->getOption('extensions'); 53 | $verbose = (bool) $input->getOption('verbose'); 54 | $this->commandService->setOutput($output); 55 | return $this->commandService->checkFluidSyntax($extension, $path, $extensions, $verbose); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Classes/Command/ListCommand.php: -------------------------------------------------------------------------------- 1 | commandService = $commandService ?? GeneralUtility::makeInstance(ObjectManager::class)->get(CommandService::class); 23 | } 24 | 25 | protected function configure() 26 | { 27 | // bool $json = false 28 | $this->setDescription('Builder: list extensions') 29 | ->setHelp('Lists extensions (active, inactive) and supports JSON output') 30 | ->addOption( 31 | 'detail', 32 | 'd', 33 | InputOption::VALUE_OPTIONAL, 34 | 'If TRUE, outputs detailed info', 35 | false 36 | )->addOption( 37 | 'active', 38 | 'a', 39 | InputOption::VALUE_OPTIONAL, 40 | 'If TRUE, outputs active (installed) extensions. Can be combined with "inactive".', 41 | false 42 | )->addOption( 43 | 'inactive', 44 | 'i', 45 | InputOption::VALUE_OPTIONAL, 46 | 'If TRUE, outputs inactive (not installed) extensions. Can be combined with "active".', 47 | false 48 | )->addOption( 49 | 'json', 50 | 'j', 51 | InputOption::VALUE_OPTIONAL, 52 | 'If TRUE, outputs information as JSON.', 53 | false 54 | ) 55 | ; 56 | } 57 | 58 | protected function execute(InputInterface $input, OutputInterface $output): int 59 | { 60 | $detail = (bool) $input->getOption('detail'); 61 | $active = (bool) $input->getOption('active'); 62 | $inactive = (bool) $input->getOption('inactive'); 63 | $json = (bool) $input->getOption('json'); 64 | $this->commandService->setOutput($output); 65 | return $this->commandService->listExtensions($detail, $active, $inactive, $json); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Classes/CodeGeneration/CodeTemplate.php: -------------------------------------------------------------------------------- 1 | identifier = $identifier; 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getIdentifier() 40 | { 41 | return $this->identifier; 42 | } 43 | 44 | /** 45 | * @param array $variables 46 | * @return void 47 | */ 48 | public function setVariables($variables) 49 | { 50 | $this->variables = $variables; 51 | } 52 | 53 | /** 54 | * @return array 55 | */ 56 | public function getVariables() 57 | { 58 | return $this->variables; 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | public function getPath() 65 | { 66 | return $this->path; 67 | } 68 | 69 | /** 70 | * @param string $path 71 | * @return void 72 | */ 73 | public function setPath($path) 74 | { 75 | $this->path = $path; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getSuffix() 82 | { 83 | return $this->suffix; 84 | } 85 | 86 | /** 87 | * @param string $suffix 88 | * @return void 89 | */ 90 | public function setSuffix($suffix) 91 | { 92 | $this->suffix = $suffix; 93 | } 94 | 95 | /** 96 | * @return string 97 | */ 98 | public function render() 99 | { 100 | $identifier = $this->getIdentifier(); 101 | $variables = $this->getVariables(); 102 | if (null === $identifier) { 103 | return null; 104 | } 105 | $filePathAndFilename = $this->getPath() . $identifier . $this->getSuffix(); 106 | $content = file_get_contents($filePathAndFilename); 107 | foreach ($variables as $name => $value) { 108 | $content = str_replace('###' . $name . '###', $value, $content); 109 | } 110 | return $content; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Classes/Result/ParserResult.php: -------------------------------------------------------------------------------- 1 | error = $error; 42 | } 43 | 44 | /** 45 | * @return \Exception 46 | */ 47 | public function getError() 48 | { 49 | return $this->error; 50 | } 51 | 52 | /** 53 | * @param boolean $valid 54 | * @return void 55 | */ 56 | public function setValid($valid) 57 | { 58 | $this->valid = $valid; 59 | } 60 | 61 | /** 62 | * @return boolean 63 | */ 64 | public function getValid() 65 | { 66 | return $this->valid; 67 | } 68 | 69 | /** 70 | * @param array $payload 71 | * @return void 72 | */ 73 | public function setPayload(array $payload) 74 | { 75 | $this->payload = $payload; 76 | } 77 | 78 | /** 79 | * @return array 80 | */ 81 | public function getPayload() 82 | { 83 | return $this->payload; 84 | } 85 | 86 | /** 87 | * @param string $payloadType 88 | * @return void 89 | */ 90 | public function setPayloadType($payloadType) 91 | { 92 | $this->payloadType = $payloadType; 93 | } 94 | 95 | /** 96 | * @return string 97 | */ 98 | public function getPayloadType() 99 | { 100 | return $this->payloadType; 101 | } 102 | 103 | /** 104 | * @return array 105 | */ 106 | public function getViewHelpers() 107 | { 108 | return $this->viewHelpers; 109 | } 110 | 111 | /** 112 | * @param array $viewHelpers 113 | * @return void 114 | */ 115 | public function setViewHelpers(array $viewHelpers) 116 | { 117 | $this->viewHelpers = $viewHelpers; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | addPsr4('FluidTYPO3\\Builder\\Tests\\Fixtures\\', __DIR__ . '/Fixtures/'); 34 | $autoloader->addPsr4('FluidTYPO3\\Builder\\Tests\\Unit\\', __DIR__ . '/Unit/'); 35 | $autoloader->addPsr4('TYPO3\\CMS\\Core\\', __DIR__ . '/../vendor/typo3/cms/typo3/sysext/core/Classes/'); 36 | $autoloader->addPsr4('TYPO3\\CMS\\Core\\Tests\\', __DIR__ . '/../vendor/typo3/cms/typo3/sysext/core/Tests/'); 37 | $autoloader->addPsr4('TYPO3\\CMS\\Extbase\\', __DIR__ . '/../vendor/typo3/cms/typo3/sysext/extbase/Classes/'); 38 | $autoloader->addPsr4('TYPO3\\CMS\\Fluid\\', __DIR__ . '/../vendor/typo3/cms/typo3/sysext/fluid/Classes/'); 39 | 40 | \FluidTYPO3\Development\Bootstrap::initialize( 41 | $autoloader, 42 | array( 43 | 'cache_core' => \FluidTYPO3\Development\Bootstrap::CACHE_PHP_NULL, 44 | 'extbase_typo3dbbackend_queries' => \FluidTYPO3\Development\Bootstrap::CACHE_NULL, 45 | 'extbase_typo3dbbackend_tablecolumns' => \FluidTYPO3\Development\Bootstrap::CACHE_NULL, 46 | 'extbase_datamapfactory_datamap' => \FluidTYPO3\Development\Bootstrap::CACHE_NULL, 47 | 'extbase_object' => \FluidTYPO3\Development\Bootstrap::CACHE_NULL, 48 | 'extbase_reflection' => \FluidTYPO3\Development\Bootstrap::CACHE_NULL, 49 | 'fluid_template' => \FluidTYPO3\Development\Bootstrap::CACHE_PHP_NULL 50 | ) 51 | ); 52 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Backend/BuildForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {csh -> f:format.raw()} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

{f:translate(key: 'header.builder')}

17 | 18 |
19 | 20 | 21 | 22 |
23 | {f:translate(key: 'header.builders')} 24 | 25 | 26 | 27 | 28 |

29 | 30 | 31 | 32 | 33 |

34 | 35 | 36 | 40 |
41 |
42 |
43 | 44 | 45 | 46 |
47 | 50 | 51 |
52 |
53 | 54 | 55 |
56 | 57 | 58 |
59 |
60 | -------------------------------------------------------------------------------- /Resources/Private/Partials/Receipt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 18 | 19 | 20 |
10 | 11 | 13 | 14 | 16 | 17 |
21 | 22 | 23 |

Next steps? Only available in English

24 |

To begin using your newly created provider extension you should perform the following steps:

25 |
    26 |
  1. Install the extension using the Extension Manager module or TYPO3 CLI commands
  2. 27 |
  3. Include the static TypoScript template in the root template(s) of the TYPO3 site(s) where you wish to use the content/page templates
  4. 28 |
  5. If you included page templates: edit the topmost page(s) where the templates are used and select "Fluidpages" in the "Backend layout" selectors
  6. 29 |
  7. If you included content templates: the templates will automatically be detected and made available in the new content element wizard
  8. 30 |
31 |

Note that if you used EXT:site to generate this provider extension and selected to create pages and resources then all the above steps will have been automatically performed.

32 |

To begin editing your templates:

33 |
    34 |
  • Open the Resources/Private/Templates/ folder of your generated extensions and start editing
  • 35 |
  • Content templates reside in the Content/ subfolder
  • 36 |
  • Page templates reside in the Page/ subfolder
  • 37 |
38 |

39 | Note that if you used EXT:site to generate this provider extension and selected to create file mount points then you can edit 40 | these files through the "file list" module. You can also create such a mount point manually - simply create it as relative 41 | to site root and pointing to the typo3conf/ext/{attributes.extensionKey}/Resources/Private/ folder. 42 |

43 |

Further documentation can be found online at https://fluidtypo3.org.

44 |
45 |
46 | -------------------------------------------------------------------------------- /Classes/Analysis/Fluid/TemplateAnalyzer.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 40 | $this->nodeCounter = $this->objectManager->get('FluidTYPO3\Builder\Analysis\Fluid\NodeCounter'); 41 | } 42 | 43 | /** 44 | * @param string $templatePathAndFilename 45 | * @return ParserResult 46 | */ 47 | public function analyzePathAndFilename($templatePathAndFilename) 48 | { 49 | $templateString = file_get_contents($templatePathAndFilename); 50 | return $this->analyze($templateString); 51 | } 52 | 53 | /** 54 | * @param string $templateString 55 | * @return ParserResult 56 | */ 57 | public function analyze($templateString) 58 | { 59 | /** @var ExposedTemplateParser $parser */ 60 | $parser = $this->getTemplateParser(); 61 | $result = new ParserResult(); 62 | try { 63 | $parsedTemplate = $parser->parse($templateString); 64 | $metrics = $this->nodeCounter->count($parser, $parsedTemplate); 65 | $this->messages = $this->nodeCounter->getMessages(); 66 | $result->setViewHelpers($parser->getUniqueViewHelpersUsed()); 67 | $result->setPayload($metrics); 68 | $result->setValid(true); 69 | $result->setPayloadType(ParserResult::PAYLOAD_METRICS); 70 | } catch (Exception $error) { 71 | $result->setValid(false); 72 | } 73 | $this->parser = $parser; 74 | return $result; 75 | } 76 | 77 | /** 78 | * @return ExposedTemplateParser 79 | */ 80 | protected function getTemplateParser() 81 | { 82 | $exposedTemplateParser = new ExposedTemplateParser(); 83 | $exposedTemplateParser->setRenderingContext(new RenderingContext()); 84 | return $exposedTemplateParser; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/Unit/CodeGeneration/CodeTemplateTest.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Builder\CodeGeneration\CodeTemplate; 28 | use FluidTYPO3\Development\AbstractTestCase; 29 | use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; 30 | 31 | /** 32 | * Class CodeTemplateTest 33 | */ 34 | class CodeTemplateTest extends AbstractTestCase 35 | { 36 | 37 | /** 38 | * @dataProvider getGetterAndSetterTestValues 39 | * @param string $property 40 | * @param mixed $value 41 | */ 42 | public function testGetterAndSetter($property, $value) 43 | { 44 | $subject = new CodeTemplate(); 45 | $setter = 'set' . ucfirst($property); 46 | $getter = 'get' . ucfirst($property); 47 | $subject->$setter($value); 48 | $this->assertEquals($value, $subject->$getter()); 49 | } 50 | 51 | /** 52 | * @return array 53 | */ 54 | public function getGetterAndSetterTestValues() 55 | { 56 | return array( 57 | array('identifier', 'test'), 58 | array('variables', array('test' => 'test')), 59 | array('path', 'test'), 60 | array('suffix', 'path') 61 | ); 62 | } 63 | 64 | /** 65 | * @dataProvider getRenderTestValues 66 | * @param string $marker 67 | * @param string $expectedOutput 68 | */ 69 | public function testRender($marker, $expectedOutput) 70 | { 71 | $subject = new CodeTemplate(); 72 | $subject->setVariables(array('foo' => $marker)); 73 | $subject->setIdentifier('CodeTemplate'); 74 | $subject->setPath(ExtensionManagementUtility::extPath('builder', 'Tests/Fixtures/Templates/')); 75 | $this->assertEquals($expectedOutput, trim($subject->render())); 76 | } 77 | 78 | /** 79 | * @return array 80 | */ 81 | public function getRenderTestValues() 82 | { 83 | return array( 84 | array('bar', 'content: bar'), 85 | array('baz', 'content: baz') 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Classes/Parser/ExposedTemplateCompiler.php: -------------------------------------------------------------------------------- 1 | sanitizeIdentifier($identifier); 20 | $this->variableCounter = 0; 21 | $generatedRenderFunctions = ''; 22 | 23 | if ($parsingState->getVariableContainer()->exists('sections')) { 24 | $sections = $parsingState->getVariableContainer()->get('sections'); 25 | // @todo refactor to $parsedTemplate->getSections() 26 | foreach ($sections as $sectionName => $sectionRootNode) { 27 | $generatedRenderFunctions .= $this->generateCodeForSection( 28 | $this->nodeConverter->convertListOfSubNodes($sectionRootNode), 29 | 'section_' . sha1($sectionName), 30 | 'section ' . $sectionName 31 | ); 32 | } 33 | } 34 | $generatedRenderFunctions .= $this->generateCodeForSection( 35 | $this->nodeConverter->convertListOfSubNodes($parsingState->getRootNode()), 36 | 'render', 37 | 'Main Render function' 38 | ); 39 | if ($parsingState->hasLayout() && method_exists($parsingState, 'getLayoutNameNode')) { 40 | $convertedLayoutNameNode = $this->nodeConverter->convert($parsingState->getLayoutNameNode()); 41 | } elseif ($parsingState->hasLayout() && method_exists($parsingState, 'getLayoutName')) { 42 | $convertedLayoutNameNode = $parsingState->getLayoutName($this->renderingContext); 43 | } else { 44 | $convertedLayoutNameNode = ['initialization' => '', 'execution' => 'NULL']; 45 | } 46 | 47 | $classDefinition = 'class FluidCache_' . $identifier . 48 | ' extends \\TYPO3\\CMS\\Fluid\\Core\\Compiler\\AbstractCompiledTemplate'; 49 | 50 | $templateCode = <<getTemplateVariableContainer(); 59 | %s 60 | return %s; 61 | } 62 | public function hasLayout() { 63 | return %s; 64 | } 65 | 66 | %s 67 | 68 | } 69 | EOD; 70 | return sprintf( 71 | $templateCode, 72 | $classDefinition, 73 | $convertedLayoutNameNode['initialization'], 74 | $convertedLayoutNameNode['execution'], 75 | ($parsingState->hasLayout() ? 'TRUE' : 'FALSE'), 76 | $generatedRenderFunctions 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Resources/Public/Javascript/jqplot.canvasAxisTickRenderer.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(a){a.jqplot.CanvasAxisTickRenderer=function(b){this.mark="outside";this.showMark=true;this.showGridline=true;this.isMinorTick=false;this.angle=0;this.markSize=4;this.show=true;this.showLabel=true;this.labelPosition="auto";this.label="";this.value=null;this._styles={};this.formatter=a.jqplot.DefaultTickFormatter;this.formatString="";this.prefix="";this.fontFamily='"Trebuchet MS", Arial, Helvetica, sans-serif';this.fontSize="10pt";this.fontWeight="normal";this.fontStretch=1;this.textColor="#666666";this.enableFontSupport=true;this.pt2px=null;this._elem;this._ctx;this._plotWidth;this._plotHeight;this._plotDimensions={height:null,width:null};a.extend(true,this,b);var c={fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily};if(this.pt2px){c.pt2px=this.pt2px}if(this.enableFontSupport){if(a.jqplot.support_canvas_text()){this._textRenderer=new a.jqplot.CanvasFontRenderer(c)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}};a.jqplot.CanvasAxisTickRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisTickRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getTop=function(b){if(this._elem){return this._elem.position().top}else{return null}};a.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisTickRenderer.prototype.setTick=function(b,d,c){this.value=b;if(c){this.isMinorTick=true}return this};a.jqplot.CanvasAxisTickRenderer.prototype.draw=function(c,f){if(!this.label){this.label=this.prefix+this.formatter(this.formatString,this.value)}if(this._elem){if(a.jqplot.use_excanvas&&window.G_vmlCanvasManager.uninitElement!==undefined){window.G_vmlCanvasManager.uninitElement(this._elem.get(0))}this._elem.emptyForce();this._elem=null}var e=f.canvasManager.getCanvas();this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e.style.textAlign="left";e.style.position="absolute";e=f.canvasManager.initCanvas(e);this._elem=a(e);this._elem.css(this._styles);this._elem.addClass("jqplot-"+this.axis+"-tick");e=null;return this._elem};a.jqplot.CanvasAxisTickRenderer.prototype.pack=function(){this._textRenderer.draw(this._elem.get(0).getContext("2d"),this.label)}})(jQuery); -------------------------------------------------------------------------------- /Resources/Public/Stylesheet/jqplot.min.css: -------------------------------------------------------------------------------- 1 | .jqplot-target{position:relative;color:#666;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:1em}.jqplot-axis{font-size:.75em}.jqplot-xaxis{margin-top:10px}.jqplot-x2axis{margin-bottom:10px}.jqplot-yaxis{margin-right:10px}.jqplot-y2axis,.jqplot-y3axis,.jqplot-y4axis,.jqplot-y5axis,.jqplot-y6axis,.jqplot-y7axis,.jqplot-y8axis,.jqplot-y9axis,.jqplot-yMidAxis{margin-left:10px;margin-right:10px}.jqplot-axis-tick,.jqplot-xaxis-tick,.jqplot-yaxis-tick,.jqplot-x2axis-tick,.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick,.jqplot-yMidAxis-tick{position:absolute;white-space:pre}.jqplot-xaxis-tick{top:0;left:15px;vertical-align:top}.jqplot-x2axis-tick{bottom:0;left:15px;vertical-align:bottom}.jqplot-yaxis-tick{right:0;top:15px;text-align:right}.jqplot-yaxis-tick.jqplot-breakTick{right:-20px;margin-right:0;padding:1px 5px 1px 5px;z-index:2;font-size:1.5em}.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick{left:0;top:15px;text-align:left}.jqplot-yMidAxis-tick{text-align:center;white-space:nowrap}.jqplot-xaxis-label{margin-top:10px;font-size:11pt;position:absolute}.jqplot-x2axis-label{margin-bottom:10px;font-size:11pt;position:absolute}.jqplot-yaxis-label{margin-right:10px;font-size:11pt;position:absolute}.jqplot-yMidAxis-label{font-size:11pt;position:absolute}.jqplot-y2axis-label,.jqplot-y3axis-label,.jqplot-y4axis-label,.jqplot-y5axis-label,.jqplot-y6axis-label,.jqplot-y7axis-label,.jqplot-y8axis-label,.jqplot-y9axis-label{font-size:11pt;margin-left:10px;position:absolute}.jqplot-meterGauge-tick{font-size:.75em;color:#999}.jqplot-meterGauge-label{font-size:1em;color:#999}table.jqplot-table-legend{margin-top:12px;margin-bottom:12px;margin-left:12px;margin-right:12px}table.jqplot-table-legend,table.jqplot-cursor-legend{background-color:rgba(255,255,255,0.6);border:1px solid #ccc;position:absolute;font-size:.75em}td.jqplot-table-legend{vertical-align:middle}td.jqplot-seriesToggle:hover,td.jqplot-seriesToggle:active{cursor:pointer}.jqplot-table-legend .jqplot-series-hidden{text-decoration:line-through}div.jqplot-table-legend-swatch-outline{border:1px solid #ccc;padding:1px}div.jqplot-table-legend-swatch{width:0;height:0;border-top-width:5px;border-bottom-width:5px;border-left-width:6px;border-right-width:6px;border-top-style:solid;border-bottom-style:solid;border-left-style:solid;border-right-style:solid}.jqplot-title{top:0;left:0;padding-bottom:.5em;font-size:1.2em}table.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em}.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px}.jqplot-highlighter-tooltip,.jqplot-canvasOverlay-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px}.jqplot-point-label{font-size:.75em;z-index:2}td.jqplot-cursor-legend-swatch{vertical-align:middle;text-align:center}div.jqplot-cursor-legend-swatch{width:1.2em;height:.7em}.jqplot-error{text-align:center}.jqplot-error-message{position:relative;top:46%;display:inline-block}div.jqplot-bubble-label{font-size:.8em;padding-left:2px;padding-right:2px;color:rgb(20%,20%,20%)}div.jqplot-bubble-label.jqplot-bubble-label-highlight{background:rgba(90%,90%,90%,0.7)}div.jqplot-noData-container{text-align:center;background-color:rgba(96%,96%,96%,0.3)} 2 | -------------------------------------------------------------------------------- /Tests/Unit/Analysis/MetricTest.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Builder\Analysis\Metric; 28 | use FluidTYPO3\Development\AbstractTestCase; 29 | use TYPO3\CMS\Extbase\Error\Message; 30 | 31 | /** 32 | * Class MetricTest 33 | */ 34 | class MetricTest extends AbstractTestCase 35 | { 36 | 37 | /** 38 | * @param string $property 39 | * @param mixed $testValue 40 | * @test 41 | * @dataProvider getPropertiesTestValues 42 | */ 43 | public function testGetterAndSetter($property, $testValue) 44 | { 45 | $instance = new Metric(); 46 | $setter = 'set' . ucfirst($property); 47 | $getter = 'get' . ucfirst($property); 48 | $instance->$setter($testValue); 49 | $this->assertEquals($testValue, $instance->$getter()); 50 | } 51 | 52 | /** 53 | * @return array 54 | */ 55 | public function getPropertiesTestValues() 56 | { 57 | return array( 58 | array('name', 'metricname'), 59 | array('value', 'metricvalue'), 60 | array('messages', array(new Message('test', 1))), 61 | array('payload', array('test')) 62 | ); 63 | } 64 | 65 | /** 66 | * @test 67 | */ 68 | public function testIncrement() 69 | { 70 | $instance = new Metric(); 71 | $instance->setValue(2); 72 | $instance->increment(); 73 | $this->assertEquals(3, $instance->getValue()); 74 | $instance->increment(2); 75 | $this->assertEquals(5, $instance->getValue()); 76 | } 77 | 78 | /** 79 | * @test 80 | */ 81 | public function testSetOnlyIfHigher() 82 | { 83 | $instance = new Metric(); 84 | $instance->setValue(5); 85 | $instance->setOnlyIfHigher(3); 86 | $this->assertEquals(5, $instance->getValue()); 87 | $instance->setOnlyIfHigher(10); 88 | $this->assertEquals(10, $instance->getValue()); 89 | } 90 | 91 | /** 92 | * @test 93 | */ 94 | public function testSetOnlyIfLower() 95 | { 96 | $instance = new Metric(); 97 | $instance->setValue(5); 98 | $instance->setOnlyIfLower(10); 99 | $this->assertEquals(5, $instance->getValue()); 100 | $instance->setOnlyIfLower(3); 101 | $this->assertEquals(3, $instance->getValue()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Tests/Unit/Analysis/Fluid/NodeCounterTest.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Builder\Analysis\Fluid\NodeCounter; 28 | use FluidTYPO3\Builder\Analysis\Metric; 29 | use FluidTYPO3\Builder\Parser\ExposedTemplateParser; 30 | use FluidTYPO3\Development\AbstractTestCase; 31 | use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; 32 | use TYPO3\CMS\Core\Utility\GeneralUtility; 33 | 34 | /** 35 | * Class NodeCounterTest 36 | */ 37 | class NodeCounterTest extends AbstractTestCase 38 | { 39 | 40 | /** 41 | * @var array 42 | */ 43 | protected $expectedMetricValues = array( 44 | NodeCounter::METRIC_TOTAL_SPLITS => 8, 45 | NodeCounter::METRIC_TOTAL_NODES => 11, 46 | NodeCounter::METRIC_VIEWHELPERS => 2, 47 | NodeCounter::METRIC_SECTIONS => 1, 48 | NodeCounter::METRIC_CONDITION_NODES => 1, 49 | NodeCounter::METRIC_NODES_PER_SECTION_AVERAGE => 3, 50 | NodeCounter::METRIC_NODES_PER_SECTION_MAXIMUM => 3, 51 | NodeCounter::METRIC_MAXIMUM_ARGUMENT_COUNT => 3, 52 | NodeCounter::METRIC_MAXIMUM_NESTING_LEVEL => 2 53 | ); 54 | 55 | /** 56 | * @return array 57 | */ 58 | protected function getPreparedFixtures() 59 | { 60 | $objectManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager'); 61 | /** @var ExposedTemplateParser $template */ 62 | $template = $objectManager->get('FluidTYPO3\\Builder\\Parser\\ExposedTemplateParser'); 63 | $fixture = ExtensionManagementUtility::extPath('builder', 'Tests/Fixtures/Templates/AnalysisFixture.html'); 64 | $parsedTemplate = $template->parse(file_get_contents($fixture)); 65 | /** @var NodeCounter $nodeCounter */ 66 | $nodeCounter = $objectManager->get('FluidTYPO3\\Builder\\Analysis\\Fluid\\NodeCounter'); 67 | return array($nodeCounter, $template, $parsedTemplate); 68 | } 69 | 70 | /** 71 | * @test 72 | */ 73 | public function testNodeCounterAgainstKnownFixture() 74 | { 75 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['f'] = ['TYPO3\\CMS\\Fluid\\ViewHelpers', 'TYPO3Fluid\\Fluid\\ViewHelpers']; 76 | /** @var NodeCounter $nodeCounter */ 77 | list ($nodeCounter, $parser, $parsedTemplate) = $this->getPreparedFixtures(); 78 | $result = $nodeCounter->count($parser, $parsedTemplate); 79 | $expectedValues = $this->expectedMetricValues; 80 | /** @var Metric $metric */ 81 | foreach ($result as $metric) { 82 | $name = $metric->getName(); 83 | $this->assertEquals($expectedValues[$name], $metric->getValue(), 'Unexpected value of ' . $name); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/Unit/CodeGeneration/Extension/ExtensionGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use FluidTYPO3\Builder\CodeGeneration\Extension\ExtensionGenerator; 28 | use FluidTYPO3\Development\AbstractTestCase; 29 | use org\bovigo\vfs\vfsStream; 30 | use org\bovigo\vfs\vfsStreamDirectory; 31 | use org\bovigo\vfs\vfsStreamWrapper; 32 | use TYPO3\CMS\Core\Utility\GeneralUtility; 33 | use TYPO3\CMS\Extbase\Object\ObjectManager; 34 | 35 | /** 36 | * Class ExtensionGeneratorTest 37 | */ 38 | class ExtensionGeneratorTest extends AbstractTestCase 39 | { 40 | 41 | /** 42 | * @throws \org\bovigo\vfs\vfsStreamException 43 | */ 44 | public static function setUpBeforeClass() 45 | { 46 | vfsStreamWrapper::register(); 47 | vfsStreamWrapper::setRoot(new vfsStreamDirectory('temp')); 48 | } 49 | 50 | /** 51 | * @test 52 | */ 53 | public function testDryRun() 54 | { 55 | /** @var ObjectManager $objectManager */ 56 | $objectManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager'); 57 | 58 | /** @var ExtensionGenerator|\PHPUnit_Framework_MockObject_MockObject $instance */ 59 | $instance = $this->getMockBuilder('FluidTYPO3\\Builder\\CodeGeneration\\Extension\\ExtensionGenerator') 60 | ->setMethods(array('getBuilderExtensionPath', 'getPreparedCodeTemplate')) 61 | ->getMock(); 62 | $codeTemplate = $this->getMockBuilder('FluidTYPO3\\Builder\\CodeGeneration\\CodeTemplate')->setMethods(array('getFilePath'))->getMock(); 63 | $codeTemplate->expects($this->any())->method('getFilePath')->will($this->returnArgument(0)); 64 | $instance->expects($this->any())->method('getBuilderExtensionPath')->will($this->returnValue(vfsStream::url('temp/'))); 65 | $instance->expects($this->any())->method('getPreparedCodeTemplate')->will($this->returnValue($codeTemplate)); 66 | $instance->injectObjectManager($objectManager); 67 | $instance->setDry(true); 68 | $instance->setConfiguration(array( 69 | 'dependencies' => array('fluidpages', 'fluidcontent', 'fluidbackend'), 70 | 'controllers' => true, 'extensionKey' => 'Vendor.Dummy' 71 | )); 72 | $result = $instance->generate(); 73 | $this->assertEquals('Built extension "dummy"', $result); 74 | } 75 | 76 | /** 77 | * @test 78 | */ 79 | public function testThrowsExceptionIfTargetDirectoryExists() 80 | { 81 | $instance = new ExtensionGenerator(); 82 | $instance->setTargetFolder(vfsStream::url('temp')); 83 | $this->setExpectedException('RuntimeException'); 84 | $instance->generate(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Classes/Analysis/Metric.php: -------------------------------------------------------------------------------- 1 | messages = $messages; 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getMessages() 55 | { 56 | return $this->messages; 57 | } 58 | 59 | /** 60 | * @param MessageInterface $message 61 | * @return Metric 62 | */ 63 | public function addMessage(MessageInterface $message) 64 | { 65 | array_push($this->messages, $message); 66 | return $this; 67 | } 68 | 69 | /** 70 | * @param string $name 71 | * @return Metric 72 | */ 73 | public function setName($name) 74 | { 75 | $this->name = $name; 76 | return $this; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getName() 83 | { 84 | return $this->name; 85 | } 86 | 87 | /** 88 | * @param mixed $value 89 | * @return Metric 90 | */ 91 | public function setValue($value) 92 | { 93 | $this->value = $value; 94 | return $this; 95 | } 96 | 97 | /** 98 | * @return mixed 99 | */ 100 | public function getValue() 101 | { 102 | return $this->value; 103 | } 104 | 105 | /** 106 | * @return array 107 | */ 108 | public function getPayload() 109 | { 110 | return $this->payload; 111 | } 112 | 113 | /** 114 | * @param array $payload 115 | * @return void 116 | */ 117 | public function setPayload(array $payload) 118 | { 119 | $this->payload = $payload; 120 | } 121 | 122 | /** 123 | * Attempt to increment $this->value if it is numeric in any way. 124 | * 125 | * @param mixed $value 126 | * @return Metric 127 | */ 128 | public function increment($value = 1) 129 | { 130 | if (true === ctype_digit($this->value) || true === is_float($value) || true === is_integer($value)) { 131 | $this->value += $value; 132 | } 133 | } 134 | 135 | /** 136 | * @param mixed $value 137 | * @return Metric 138 | */ 139 | public function setOnlyIfHigher($value) 140 | { 141 | $this->value = max($this->value, $value); 142 | } 143 | 144 | /** 145 | * @param mixed $value 146 | * @return Metric 147 | */ 148 | public function setOnlyIfLower($value) 149 | { 150 | $this->value = min($this->value, $value); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Resources/Public/Javascript/jqplot.pointLabels.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(c){c.jqplot.PointLabels=function(e){this.show=c.jqplot.config.enablePlugins;this.location="n";this.labelsFromSeries=false;this.seriesLabelIndex=null;this.labels=[];this._labels=[];this.stackedValue=false;this.ypadding=6;this.xpadding=6;this.escapeHTML=true;this.edgeTolerance=-5;this.formatter=c.jqplot.DefaultTickFormatter;this.formatString="";this.hideZeros=false;this._elems=[];c.extend(true,this,e)};var a=["nw","n","ne","e","se","s","sw","w"];var d={nw:0,n:1,ne:2,e:3,se:4,s:5,sw:6,w:7};var b=["se","s","sw","w","nw","n","ne","e"];c.jqplot.PointLabels.init=function(j,h,f,g,i){var e=c.extend(true,{},f,g);e.pointLabels=e.pointLabels||{};if(this.renderer.constructor===c.jqplot.BarRenderer&&this.barDirection==="horizontal"&&!e.pointLabels.location){e.pointLabels.location="e"}this.plugins.pointLabels=new c.jqplot.PointLabels(e.pointLabels);this.plugins.pointLabels.setLabels.call(this)};c.jqplot.PointLabels.prototype.setLabels=function(){var f=this.plugins.pointLabels;var h;if(f.seriesLabelIndex!=null){h=f.seriesLabelIndex}else{if(this.renderer.constructor===c.jqplot.BarRenderer&&this.barDirection==="horizontal"){h=(this._plotData[0].length<3)?0:this._plotData[0].length-1}else{h=(this._plotData.length===0)?0:this._plotData[0].length-1}}f._labels=[];if(f.labels.length===0||f.labelsFromSeries){if(f.stackedValue){if(this._plotData.length&&this._plotData[0].length){for(var e=0;eB||s+C>m){z.remove()}z=null;f=null}}};c.jqplot.postSeriesInitHooks.push(c.jqplot.PointLabels.init);c.jqplot.postDrawSeriesHooks.push(c.jqplot.PointLabels.draw)})(jQuery); -------------------------------------------------------------------------------- /Classes/Command/BuildCommand.php: -------------------------------------------------------------------------------- 1 | commandService = $commandService ?? GeneralUtility::makeInstance(ObjectManager::class)->get(CommandService::class); 24 | } 25 | 26 | protected function configure() 27 | { 28 | $this->setDescription('Builder: generate provider extension') 29 | ->setHelp('Creates a new Flux provider extension and bootstraps it with assets/classes according to given toggles.') 30 | ->addArgument( 31 | 'extension', 32 | InputArgument::REQUIRED, 33 | 'Extension key to generate (lowercase_underscored format)' 34 | )->addArgument( 35 | 'author', 36 | InputArgument::REQUIRED, 37 | 'Author (Firstname Lastname email@address.com)' 38 | ) 39 | ->addOption( 40 | 'title', 41 | 't', 42 | InputOption::VALUE_OPTIONAL, 43 | 'Title of the resulting extension, by default "Provider extension for $enabledFeaturesList"' 44 | )->addOption( 45 | 'description', 46 | 'd', 47 | InputOption::VALUE_OPTIONAL, 48 | 'Description for extension, by default "Provider extension for $enabledFeaturesList"' 49 | )->addOption( 50 | 'useVhs', 51 | 'vhs', 52 | InputOption::VALUE_OPTIONAL, 53 | 'If TRUE, adds the VHS extension as dependency - recommended, on by default', 54 | true 55 | )->addOption( 56 | 'pages', 57 | 'p', 58 | InputOption::VALUE_OPTIONAL, 59 | 'If TRUE, generates basic files for implementing Fluid Page templates', 60 | true 61 | )->addOption( 62 | 'content', 63 | 'c', 64 | InputOption::VALUE_OPTIONAL, 65 | 'IF TRUE, generates basic files for implementing Fluid Content templates', 66 | true 67 | )->addOption( 68 | 'controllers', 69 | 'cnt', 70 | InputOption::VALUE_OPTIONAL, 71 | 'If TRUE, generates controllers for each enabled feature', 72 | true 73 | )->addOption( 74 | 'dry', 75 | 'dry', 76 | InputOption::VALUE_OPTIONAL, 77 | 'If TRUE performs a dry run without writing files;reports which files would have been written', 78 | false 79 | ); 80 | } 81 | 82 | /** 83 | * Execute scheduler tasks 84 | * 85 | * @param InputInterface $input 86 | * @param OutputInterface $output 87 | * @return int 88 | */ 89 | public function execute(InputInterface $input, OutputInterface $output): int 90 | { 91 | $extension = $input->getArgument('extension'); 92 | $author = $input->getArgument('author'); 93 | $title = $input->getOption('title'); 94 | $description = $input->getOption('title'); 95 | $useVhs = (boolean) $input->getOption('useVhs'); 96 | $pages = (boolean) $input->getOption('pages'); 97 | $content = (boolean) $input->getOption('content'); 98 | $controllers = (boolean) $input->getOption('controllers'); 99 | $verbose = (boolean) $input->getOption('verbose'); 100 | $dry = (boolean) $input->getOption('dry'); 101 | 102 | $this->commandService->setOutput($output); 103 | return $this->commandService->generateProviderExtension( 104 | $extension, 105 | $author, 106 | $title, 107 | $description, 108 | $controllers, 109 | $pages, 110 | $content, 111 | $useVhs, 112 | $dry, 113 | $verbose 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Classes/Parser/ExposedTemplateParser.php: -------------------------------------------------------------------------------- 1 | setRenderingContext(GeneralUtility::makeInstance(ObjectManager::class)->get(RenderingContext::class)); 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getUniqueViewHelpersUsed() 37 | { 38 | $names = []; 39 | foreach ($this->viewHelpersUsed as $metadata) { 40 | list ($namespace, $viewhelper, , ) = array_values($metadata); 41 | $id = $namespace . ':' . $viewhelper; 42 | if (false === in_array($id, $names)) { 43 | $names[] = $id; 44 | } 45 | } 46 | return $names; 47 | } 48 | 49 | /** 50 | * Parses a given template string and returns a parsed template object. 51 | * 52 | * The resulting ParsedTemplate can then be rendered by calling evaluate() on it. 53 | * 54 | * Normally, you should use a subclass of AbstractTemplateView instead of calling the 55 | * TemplateParser directly. 56 | * 57 | * @param string $templateString The template to parse as a string 58 | * @param string|null $templateIdentifier If the template has an identifying string it can be passed here to improve error reporting. 59 | * @return ParsingState Parsed template 60 | * @throws Exception 61 | */ 62 | public function parse($templateString, $templateIdentifier = null) 63 | { 64 | if (!is_string($templateString)) { 65 | throw new Exception('Parse requires a template string as argument, ' . gettype($templateString) . ' given.', 1224237899); 66 | } 67 | try { 68 | $this->reset(); 69 | 70 | $templateString = $this->preProcessTemplateSource($templateString); 71 | 72 | $splitTemplate = $this->splitTemplate = $this->splitTemplateAtDynamicTags($templateString); 73 | $parsingState = $this->buildObjectTree($splitTemplate, self::CONTEXT_OUTSIDE_VIEWHELPER_ARGUMENTS); 74 | } catch (Exception $error) { 75 | throw $this->createParsingRelatedExceptionWithContext($error, $templateIdentifier); 76 | } 77 | $this->parsedTemplates[$templateIdentifier] = $parsingState; 78 | return $parsingState; 79 | } 80 | 81 | /** 82 | * @return RenderingContextInterface 83 | */ 84 | public function getRenderingContext() 85 | { 86 | return $this->renderingContext; 87 | } 88 | 89 | /** 90 | * Initialize the given ViewHelper and adds it to the current node and to 91 | * the stack. 92 | * 93 | * @param ParsingState $state Current parsing state 94 | * @param string $namespaceIdentifier Namespace identifier - being looked up in $this->namespaces 95 | * @param string $methodIdentifier Method identifier 96 | * @param array $argumentsObjectTree Arguments object tree 97 | * @return ViewHelperNode]null 98 | * @throws Exception 99 | */ 100 | protected function initializeViewHelperAndAddItToStack( 101 | ParsingState $state, 102 | $namespaceIdentifier, 103 | $methodIdentifier, 104 | $argumentsObjectTree 105 | ) { 106 | $this->viewHelpersUsed[] = [ 107 | 'namespace' => $namespaceIdentifier, 108 | 'viewhelper' => $methodIdentifier 109 | ]; 110 | return parent::initializeViewHelperAndAddItToStack($state, $namespaceIdentifier, $methodIdentifier, $argumentsObjectTree); 111 | } 112 | 113 | /** 114 | * @param array $splitTemplate 115 | * @param integer $context 116 | * @return ParsingState 117 | */ 118 | public function buildObjectTree(array $splitTemplate, $context) 119 | { 120 | return parent::buildObjectTree($splitTemplate, $context); 121 | } 122 | 123 | /** 124 | * @return array 125 | */ 126 | public function getSplitTemplate() 127 | { 128 | return $this->splitTemplate; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Classes/CodeGeneration/AbstractCodeGenerator.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 33 | } 34 | 35 | /** 36 | * @param boolean $dry 37 | * @return void 38 | */ 39 | public function setDry($dry) 40 | { 41 | $this->dry = $dry; 42 | } 43 | 44 | /** 45 | * @param boolean $verbose 46 | * @return void 47 | */ 48 | public function setVerbose($verbose) 49 | { 50 | $this->verbose = $verbose; 51 | } 52 | 53 | /** 54 | * @param string $folderPath 55 | * @return boolean 56 | * @throws \Exception 57 | */ 58 | public function createFolder($folderPath) 59 | { 60 | if (true === $this->dry) { 61 | return true; 62 | } 63 | try { 64 | GeneralUtility::mkdir_deep($folderPath); 65 | } catch (\InvalidArgumentException $exception) { 66 | throw new \Exception('Unable to create directory "' . $folderPath . '"', 1371692697); 67 | } 68 | return true; 69 | } 70 | 71 | /** 72 | * @param string $filePathAndFilename 73 | * @param string $content 74 | * @return boolean 75 | * @throws \Exception 76 | */ 77 | public function createFile($filePathAndFilename, $content) 78 | { 79 | if (true === $this->dry) { 80 | return true; 81 | } 82 | $folderPath = pathinfo($filePathAndFilename, PATHINFO_DIRNAME); 83 | if (false === is_dir($folderPath)) { 84 | $this->createFolder($folderPath); 85 | } 86 | $createdFile = GeneralUtility::writeFile($filePathAndFilename, $content); 87 | if (false === $createdFile) { 88 | throw new \Exception('Unable to create file "' . $filePathAndFilename . '"', 1371695066); 89 | } 90 | return true; 91 | } 92 | 93 | /** 94 | * @param string $localRelativePathAndFilename 95 | * @param string $destinationPathAndFilename 96 | * @return boolean 97 | * @throws \Exception 98 | */ 99 | public function copyFile($localRelativePathAndFilename, $destinationPathAndFilename) 100 | { 101 | if (true === $this->dry) { 102 | return true; 103 | } 104 | $folderPath = pathinfo($destinationPathAndFilename, PATHINFO_DIRNAME); 105 | if (false === is_dir($folderPath)) { 106 | $this->createFolder($folderPath); 107 | } 108 | $localFile = $this->getBuilderExtensionPath() . $localRelativePathAndFilename; 109 | $fileCopied = copy($localFile, $destinationPathAndFilename); 110 | if (false === $fileCopied) { 111 | throw new \Exception( 112 | 'Unable to copy file "' . $localFile . '" to "' . $destinationPathAndFilename . '"', 113 | 1371695897 114 | ); 115 | } 116 | return true; 117 | } 118 | 119 | /** 120 | * @return string 121 | */ 122 | protected function getBuilderExtensionPath() 123 | { 124 | return rtrim(ExtensionManagementUtility::extPath('builder'), '/') . '/'; 125 | } 126 | 127 | /** 128 | * @param string $filePathAndFilename 129 | * @return void 130 | */ 131 | public function save($filePathAndFilename) 132 | { 133 | $code = $this->generate(); 134 | $this->createFile($filePathAndFilename, $code); 135 | } 136 | 137 | /** 138 | * @param string $identifier 139 | * @param array $variables 140 | * @return CodeTemplate 141 | */ 142 | protected function getPreparedCodeTemplate($identifier, $variables) 143 | { 144 | /** @var CodeTemplate $template */ 145 | $template = $this->objectManager->get('FluidTYPO3\Builder\CodeGeneration\CodeTemplate'); 146 | $template->setPath(ExtensionManagementUtility::extPath('builder', 'Resources/Private/CodeTemplates/')); 147 | $template->setIdentifier($identifier); 148 | $template->setVariables($variables); 149 | return $template; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Backend/Syntax.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {csh -> f:format.raw()} 9 | 10 | 11 | 12 | 13 | 14 | File 15 | ViewHelpers 16 | Cached Size 17 | Sections 18 | Conditions 19 | Splits 20 | Nodes 21 | Var Reads 22 | Expressions 23 | Max Nesting 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |

{format -> v:format.case(case: 'ucfirst')}

38 | {formatReports.reports -> v:iterator.first() -> f:variable(name: 'first')} 39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 48 | 49 | 73 | 81 | 82 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {filterFile -> v:format.substring(start: basePathLength)} 64 | 65 | 66 |
67 | 71 |
72 |
74 | 75 |
76 | {formatReports.json -> f:format.raw()} 77 |
78 |
79 |
80 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | 102 | 112 | 115 | 116 | 117 | 118 | 119 |
92 | {filePathAndFilename} 93 | 95 |
    96 | 97 |
  • {viewHelper}
  • 98 |
    99 |
100 | Total: {result.viewHelpers -> f:count()} 101 |
113 | {result.payload.{metric}.value} 114 |
120 |
121 | 122 | 123 | 124 | {f:if(condition: result.valid, then: 'success', else: 'danger') -> v:variable.set(name: 'class')} 125 | {f:if(condition: result.valid, then: 'All is well', else: result.error) -> v:variable.set(name: 'body')} 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 |
135 |

{title}

136 |
137 | {body} 138 |
139 |
140 |
141 | -------------------------------------------------------------------------------- /Classes/CodeGeneration/AbstractClassGenerator.php: -------------------------------------------------------------------------------- 1 | classAnalysisService = $classAnalysisService; 52 | } 53 | 54 | /** 55 | * @param string $name 56 | * @return void 57 | * @abstract 58 | */ 59 | public function setClassName($name) 60 | { 61 | $this->name = $name; 62 | } 63 | 64 | /** 65 | * @param string $author 66 | */ 67 | public function setAuthor($author) 68 | { 69 | $this->author = $author; 70 | } 71 | 72 | /** 73 | * @param string $package 74 | */ 75 | public function setPackage($package) 76 | { 77 | $this->package = $package; 78 | } 79 | 80 | 81 | /** 82 | * @param $attributes 83 | * @return void 84 | * @abstract 85 | */ 86 | public function setClassAttributes($attributes) 87 | { 88 | $this->attributes = $attributes; 89 | } 90 | 91 | 92 | /** 93 | * @param string $templateIdentifier 94 | * @param array $variables 95 | * @return void 96 | * @abstract 97 | */ 98 | public function appendMethodFromSourceTemplate($templateIdentifier, $variables = []) 99 | { 100 | $name = true === isset($variables['name']) ? $variables['name'] : basename($templateIdentifier); 101 | $template = $this->getPreparedCodeTemplate($templateIdentifier, $variables); 102 | $code = $template->render(); 103 | $this->methods[$name] = $code; 104 | } 105 | 106 | /** 107 | * @param string $name 108 | * @param string $type 109 | * @param string $visibility 110 | * @return void 111 | * @abstract 112 | */ 113 | public function appendProperty($name, $type, $visibility = 'protected') 114 | { 115 | $code = "\t/**\n\t * @var $" . $name . ' ' . $type . "\n\t */\n\t" . $visibility . ' $' . $name . ';'; 116 | $this->properties[$name] = $code; 117 | } 118 | 119 | /** 120 | * @param string $filePathAndFilename 121 | * @return void 122 | */ 123 | public function save($filePathAndFilename) 124 | { 125 | $code = $this->generate(); 126 | $shouldBeWritten = false; 127 | if (false === file_exists($filePathAndFilename)) { 128 | $shouldBeWritten = true; 129 | } else { 130 | $contents = file_get_contents($filePathAndFilename); 131 | if (false !== strpos($contents, '@protection off')) { 132 | unlink($filePathAndFilename); 133 | // class file contains marker which allows overwriting without further ado 134 | $shouldBeWritten = true; 135 | } 136 | } 137 | if (true === $shouldBeWritten) { 138 | GeneralUtility::writeFile($filePathAndFilename, $code); 139 | } 140 | } 141 | 142 | /** 143 | * @param string $template 144 | * @param string $className 145 | * @return string 146 | */ 147 | public function renderClass($template, $className) 148 | { 149 | if (null === $className) { 150 | return null; 151 | } 152 | $properties = array_map('trim', $this->properties); 153 | $methods = array_map('trim', $this->methods); 154 | // @todo check if needed 155 | // $this->appendCommonTestMethods(); 156 | $variables = [ 157 | 'class' => $className, 158 | 'author' => $this->author, 159 | 'year' => date('Y', time()), 160 | 'protection' => 'off', 161 | 'package' => $this->package, 162 | 'properties' => implode("\n\n\t", $properties), 163 | 'methods' => implode("\n\n\t", $methods) 164 | ]; 165 | $template = $this->getPreparedCodeTemplate($template, $variables); 166 | return $template->render(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Builder 8 | 9 | 10 | Builder 11 | 12 | 13 | Builder 14 | 15 | 16 | The Builder provides a syntax tester and a provider extension builder. 17 | 18 | 19 | In this module you can perform the following actions: 20 | 21 | 22 | Select extension 23 | 24 | 25 | Select script type(s) 26 | 27 | 28 | Check PHP file syntax 29 | 30 | 31 | Check Fluid template syntax 32 | 33 | 34 | Profile Fluid syntax 35 | 36 | 37 | Perform validations 38 | 39 | 40 | Extension key 41 | 42 | 43 | You should follow the pattern Vendor.ExtensionKey and this will generate a folder extension_key and namespaces Vendor\ExtensionKey\Controller 44 | 45 | 46 | Author name and email 47 | 48 | 49 | Brief title 50 | 51 | 52 | Brief description 53 | 54 | 55 | Be verbose about actions taken 56 | 57 | 58 | Toggles 59 | 60 | 61 | Include page templates 62 | 63 | 64 | Include content templates 65 | 66 | 67 | Include backend module templates 68 | 69 | 70 | Create controllers for enabled FluidTYPO3 features 71 | 72 | 73 | Set the VHS extension as dependency 74 | 75 | 76 | Set the FluidcontentCore extension as dependency 77 | 78 | 79 | Build behavior 80 | 81 | 82 | Dry run - do not build actual files 83 | 84 | 85 | Create provider extension 86 | 87 | 88 | You chose to install the Provider extension - please make sure you clear all caches RIGHT NOW. 89 | 90 | 91 | Builder - Syntax validator 92 | 93 | 94 | Builder - Provider extension builder 95 | 96 | 97 | Create FluidTYPO3 Provider Extension 98 | 99 | 100 | Syntax check reports 101 | 102 | 103 | Validator 104 | 105 | 106 | Build results 107 | 108 | 109 | No reports 110 | 111 | 112 | Passed! 113 | 114 | 115 | %s file(s) passed! 116 | 117 | 118 | No 119 | 120 | 121 | Yes 122 | 123 | 124 | Option 125 | 126 | 127 | Selected value 128 | 129 | 130 | Re-Run 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /Classes/Service/SyntaxService.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 32 | } 33 | 34 | /** 35 | * Syntax checks a Fluid template file by attempting 36 | * to load the file and retrieve a parsed template, which 37 | * will cause traversal of the entire syntax node tree 38 | * and report any errors about missing or unknown arguments. 39 | * 40 | * Will NOT, however, report errors which are caused by 41 | * variables assigned to the template (there will be no 42 | * variables while building the syntax tree and listening 43 | * for errors). 44 | * 45 | * @param string $filePathAndFilename 46 | * @return FluidParserResult 47 | * @throws \Exception 48 | */ 49 | public function syntaxCheckFluidTemplateFile($filePathAndFilename) 50 | { 51 | /** @var FluidParserResult $result */ 52 | $result = $this->objectManager->get(FluidParserResult::class); 53 | try { 54 | $parser = $this->objectManager->get(ExposedTemplateParser::class); 55 | $context = $parser->getRenderingContext(); 56 | $parsedTemplate = $parser->parse(file_get_contents($filePathAndFilename)); 57 | $result->setLayoutName($parsedTemplate->getLayoutName($context)); 58 | $result->setNamespaces($context->getViewHelperResolver()->getNamespaces()); 59 | $result->setCompilable($parsedTemplate->isCompilable()); 60 | } catch (\TYPO3Fluid\Fluid\Core\Parser\Exception $error) { 61 | $result->setError($error); 62 | $result->setValid(false); 63 | } 64 | return $result; 65 | } 66 | 67 | /** 68 | * @param string $path 69 | * @param string $formats 70 | * @return FluidParserResult[] 71 | */ 72 | public function syntaxCheckFluidTemplateFilesInPath($path, $formats) 73 | { 74 | $files = GlobUtility::getFilesRecursive($path, $formats); 75 | $results = []; 76 | $pathLength = strlen($path); 77 | foreach ($files as $filePathAndFilename) { 78 | $shortFilename = substr($filePathAndFilename, $pathLength); 79 | $results[$shortFilename] = $this->syntaxCheckFluidTemplateFile($filePathAndFilename); 80 | } 81 | return $results; 82 | } 83 | 84 | /** 85 | * @param string $filePathAndFilename 86 | * @return FluidParserResult 87 | * @throws \Exception 88 | */ 89 | public function syntaxCheckPhpFile($filePathAndFilename) 90 | { 91 | /** @var FluidParserResult $result */ 92 | $result = $this->objectManager->get('FluidTYPO3\Builder\Result\FluidParserResult'); 93 | $command = 'php --define error_reporting=0 -le ' . $filePathAndFilename; 94 | $code = $this->executeCommandAndReturnZeroOrStringMessage($command); 95 | if (0 !== $code) { 96 | $output = []; 97 | $this->executeCommandAndReturnZeroOrStringMessage('php -l ' . $filePathAndFilename . ' 2>&1', $output); 98 | $error = new \Exception(array_shift($output), $code); 99 | $result->setValid(false); 100 | $result->setError($error); 101 | } 102 | return $result; 103 | } 104 | 105 | /** 106 | * @param string $extensionKey 107 | * @return FluidParserResult[] 108 | */ 109 | public function syntaxCheckPhpFilesInExtension($extensionKey) 110 | { 111 | $path = ExtensionManagementUtility::extPath($extensionKey); 112 | return $this->syntaxCheckPhpFilesInPath($path); 113 | } 114 | 115 | /** 116 | * @param string $path 117 | * @return FluidParserResult[] 118 | */ 119 | public function syntaxCheckPhpFilesInPath($path) 120 | { 121 | $files = GlobUtility::getFilesRecursive($path, 'php'); 122 | $files = array_values($files); 123 | $results = []; 124 | foreach ($files as $filePathAndFilename) { 125 | $results[$filePathAndFilename] = $this->syntaxCheckPhpFile($filePathAndFilename); 126 | } 127 | return $results; 128 | } 129 | 130 | /** 131 | * @param FluidParserResult[] $results 132 | * @return integer 133 | */ 134 | public function countErrorsInResultCollection(array $results) 135 | { 136 | $count = 0; 137 | foreach ($results as $result) { 138 | if (false === $result->getValid()) { 139 | ++ $count; 140 | } 141 | } 142 | return $count; 143 | } 144 | 145 | /** 146 | * @param string $command 147 | * @param array $output 148 | * @return integer 149 | */ 150 | protected function executeCommandAndReturnZeroOrStringMessage($command, &$output = []) 151 | { 152 | $code = 0; 153 | exec($command, $output, $code); 154 | return $code; 155 | } 156 | 157 | /** 158 | * @param RenderingContextInterface $renderingContext 159 | * @return ExposedTemplateParser 160 | */ 161 | protected function getTemplateParser(RenderingContextInterface $renderingContext) 162 | { 163 | if (version_compare(ExtensionManagementUtility::getExtensionVersion('core'), 8, '>')) { 164 | $exposedTemplateParser = new ExposedTemplateParser(); 165 | $exposedTemplateParser->setRenderingContext($renderingContext); 166 | } else { 167 | $exposedTemplateParser = new ExposedTemplateParserLegacy(); 168 | } 169 | return $exposedTemplateParser; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Builder: Development Support Utilities 4 | ====================================== 5 | 6 | [![Build Status](https://img.shields.io/travis/FluidTYPO3/builder.svg?style=flat-square&label=package)](https://travis-ci.org/FluidTYPO3/builder) [![Coverage Status](https://img.shields.io/coveralls/FluidTYPO3/builder/development.svg?style=flat-square)](https://coveralls.io/r/FluidTYPO3/builder) [![Build Status](https://img.shields.io/travis/FluidTYPO3/fluidtypo3-testing.svg?style=flat-square&label=framework)](https://travis-ci.org/FluidTYPO3/fluidtypo3-testing/) [![Coverage Status](https://img.shields.io/coveralls/FluidTYPO3/fluidtypo3-testing/master.svg?style=flat-square)](https://coveralls.io/r/FluidTYPO3/fluidtypo3-testing) 7 | 8 | ## Usage 9 | 10 | Execution is done via CommandControllers. Those are invoked by executing the TYPO3 cli-script: 11 | 12 | ```bash 13 | php ./typo3/cli_dispatch.phpsh NAMESPACE COMMAND 14 | ``` 15 | 16 | ### Commands for Extension extbase 17 | 18 | ```bash 19 | php ./typo3/cli_dispatch.phpsh extbase help 20 | Extbase 6.1.0 21 | usage: ./cli_dispatch.phpsh extbase 22 | 23 | The following commands are currently available: 24 | 25 | EXTENSION "EXTBASE": 26 | ------------------------------------------------------------------------------- 27 | help Display help for a command 28 | 29 | 30 | EXTENSION "BUILDER": 31 | ------------------------------------------------------------------------------- 32 | builder:fluidsyntax Syntax check Fluid template 33 | builder:phpsyntax Syntax check PHP code 34 | builder:providerextension Builds a ProviderExtension 35 | 36 | See './cli_dispatch.phpsh extbase help ' for more information about a specific command. 37 | ``` 38 | 39 | #### Fluid syntax checker 40 | 41 | ```bash 42 | php typo3/cli_dispatch.phpsh extbase help builder:fluidsyntax 43 | 44 | Syntax check Fluid template 45 | 46 | COMMAND: 47 | builder:builder:fluidsyntax 48 | 49 | USAGE: 50 | ./cli_dispatch.phpsh extbase builder:fluidsyntax [] 51 | 52 | OPTIONS: 53 | --extension Optional extension key (if path is included, only files 54 | in that path in this extension are checked) 55 | --path file or folder path (if extensionKey is included, path 56 | is relative to this extension) 57 | --extensions If provided, this CSV list of file extensions are 58 | considered Fluid templates 59 | --verbose If TRUE, outputs more information about each file check 60 | - default is to only output errors 61 | 62 | DESCRIPTION: 63 | Syntax check Fluid template 64 | 65 | Checks one template file, all templates in 66 | an extension or a sub-path (which can be used 67 | ``` 68 | 69 | #### PHP Syntax checker 70 | 71 | ``` 72 | php typo3/cli_dispatch.phpsh extbase help builder:phpsyntax 73 | 74 | Syntax check PHP code 75 | 76 | COMMAND: 77 | builder:builder:phpsyntax 78 | 79 | USAGE: 80 | ./cli_dispatch.phpsh extbase builder:phpsyntax [] 81 | 82 | OPTIONS: 83 | --extension Optional extension key (if path is included, only files 84 | in that path in this extension are checked) 85 | --path file or folder path (if extensionKey is included, path 86 | is relative to this extension) 87 | --verbose If TRUE, outputs more information about each file check 88 | - default is to only output errors 89 | 90 | DESCRIPTION: 91 | Syntax check PHP code 92 | 93 | Checks PHP source files in $path, if extension 94 | key is also given, only files in that path relative 95 | ``` 96 | 97 | #### Generate a FluidTYPO3 provider extension 98 | 99 | This may be the most important command available. It allows you to generate a stub extension which is fully capable 100 | of being used as provider for fluidpages, fluidcontent and fluidbackend extensions. 101 | 102 | ```bash 103 | php typo3/cli_dispatch.phpsh extbase help builder:providerextension 104 | 105 | Builds a ProviderExtension 106 | 107 | COMMAND: 108 | builder:builder:providerextension 109 | 110 | USAGE: 111 | ./cli_dispatch.phpsh extbase builder:providerextension [] 112 | 113 | ARGUMENTS: 114 | --extension-key The extension key which should be generated. Must not 115 | exist. 116 | --author The author of the extension, in the format "Name 117 | Lastname " with optional company name, 118 | in which case form is "Name Lastname , 119 | Company Name" 120 | 121 | OPTIONS: 122 | --title The title of the resulting extension, by default 123 | "Provider extension for $enabledFeaturesList" 124 | --description The description of the resulting extension, by default 125 | "Provider extension for $enabledFeaturesList" 126 | --use-vhs If TRUE, adds the VHS extension as dependency - 127 | recommended, on by default 128 | --pages If TRUE, generates basic files for implementing Fluid 129 | Page templates 130 | --content IF TRUE, generates basic files for implementing Fluid 131 | Content templates 132 | --backend If TRUE, generates basic files for implementing Fluid 133 | Backend modules 134 | --controllers If TRUE, generates controllers for each enabled feature. 135 | Enabling $backend will always generate a controller 136 | regardless of this toggle. 137 | --minimum-version The minimum required core version for this extension, 138 | defaults to latest LTS (currently 4.5) 139 | --dry If TRUE, performs a dry run: does not write any files 140 | but reports which files would have been written 141 | --verbose If FALSE, suppresses a lot of the otherwise output 142 | messages (to STDOUT) 143 | --git If TRUE, initialises the newly created extension 144 | directory as a Git repository and commits all files. You 145 | can then "git add remote origin " and "git push 146 | origin master -u" to push the initial state 147 | --travis If TRUE, generates a Travis-CI build script which uses 148 | Fluid Powered TYPO3 coding standards analysis and code 149 | inspections to automate testing on Travis-CI 150 | 151 | DESCRIPTION: 152 | Builds a ProviderExtension 153 | 154 | The resulting extension will contain source code 155 | and configuration options needed by the various 156 | toggles. Each of these toggles enable/disable 157 | generation of source code and configuration for 158 | ``` 159 | -------------------------------------------------------------------------------- /Classes/Command/BuilderCommandController.php: -------------------------------------------------------------------------------- 1 | commandService = $commandService ?? GeneralUtility::makeInstance(ObjectManager::class)->get(CommandService::class); 32 | } 33 | 34 | /** 35 | * @param SyntaxService $syntaxService 36 | * @return void 37 | */ 38 | public function injectSyntaxService(SyntaxService $syntaxService) 39 | { 40 | $this->syntaxService = $syntaxService; 41 | } 42 | 43 | /** 44 | * @param ExtensionService $extensionService 45 | * @return void 46 | */ 47 | public function injectExtensionService(ExtensionService $extensionService) 48 | { 49 | $this->extensionService = $extensionService; 50 | } 51 | 52 | /** 53 | * Syntax check Fluid template 54 | * 55 | * Checks one template file, all templates in 56 | * an extension or a sub-path (which can be used 57 | * with an extension key for a relative path). 58 | * If left out, it will lint ALL templates in 59 | * EVERY local extension. 60 | * 61 | * @param string $extension Optional extension key (if path is set too it will apply to sub-folders in extension) 62 | * @param string $path file or folder path (if extensionKey is included, path is relative to this extension) 63 | * @param string $extensions If provided, this CSV list of file extensions are considered Fluid templates 64 | * @param boolean $verbose If TRUE outputs more information about each file check - default is to only output errors 65 | * @throws \RuntimeException 66 | * @return void 67 | */ 68 | public function fluidSyntaxCommand($extension = null, $path = null, $extensions = 'html,xml,txt', $verbose = false) 69 | { 70 | $this->commandService->setResponse($this->response); 71 | $this->commandService->checkFluidSyntax($extension, $path, $extensions, $verbose); 72 | } 73 | 74 | /** 75 | * Syntax check PHP code 76 | * 77 | * Checks PHP source files in $path, if extension 78 | * key is also given, only files in that path relative 79 | * to that extension are checked. 80 | * 81 | * @param string $extension Optional extension key (if path is set too it will apply to sub-folders in extension) 82 | * @param string $path file or folder path (if extensionKey is included, path is relative to this extension) 83 | * @param boolean $verbose If TRUE outputs more information about each file check - default is to only output errors 84 | * @return void 85 | */ 86 | public function phpsyntaxCommand($extension = null, $path = null, $verbose = false) 87 | { 88 | $this->commandService->setResponse($this->response); 89 | $this->commandService->checkPhpSyntax($extension, $path, $verbose); 90 | } 91 | 92 | /** 93 | * Lists installed Extensions. The output defaults to text and is new-line separated. 94 | * 95 | * @param boolean $detail If TRUE, the command will give detailed information such as version and state 96 | * @param boolean $active If TRUE, the command will give information about active extensions only 97 | * @param boolean $inactive If TRUE, the command will give information about inactive extensions only 98 | * @param boolean $json If TRUE, the command will return a json object-string 99 | * @throws \Exception 100 | * @return void 101 | */ 102 | public function listCommand($detail = false, $active = null, $inactive = false, $json = false) 103 | { 104 | $detail = (boolean) $detail; 105 | $active = (boolean) $active; 106 | $inactive = (boolean) $inactive; 107 | $json = (boolean) $json; 108 | 109 | $this->commandService->setResponse($this->response); 110 | $this->commandService->listExtensions($detail, $active, $inactive, $json); 111 | } 112 | 113 | /** 114 | * Builds a ProviderExtension 115 | * 116 | * The resulting extension will contain source code 117 | * and configuration options needed by the various 118 | * toggles. Each of these toggles enable/disable 119 | * generation of source code and configuration for 120 | * that particular feature. 121 | * 122 | * @param string $extensionKey The extension identity which should be generated. Must not exist. Must be in the format "VendorName.ExtensionName". 123 | * @param string $author The author of the extension, in the format "Name Lastname " with optional 124 | * company name, in which case form is "Name Lastname , Company Name" 125 | * @param string $title Title of the resulting extension, by default "Provider extension for $enabledFeaturesList" 126 | * @param string $description Description for extension, by default "Provider extension for $enabledFeaturesList" 127 | * @param boolean $useVhs If TRUE, adds the VHS extension as dependency - recommended, on by default 128 | * @param boolean $pages If TRUE, generates basic files for implementing Fluid Page templates 129 | * @param boolean $content IF TRUE, generates basic files for implementing Fluid Content templates 130 | * @param boolean $controllers If TRUE, generates controllers for each enabled feature. Enabling $backend will 131 | * always generate a controller regardless of this toggle. 132 | * @param boolean $dry If TRUE performs a dry run without writing files;reports which files would have been written 133 | * @param boolean $verbose If FALSE, suppresses a lot of the otherwise output messages (to STDOUT) 134 | * @return void 135 | */ 136 | public function providerExtensionCommand( 137 | $extensionKey, 138 | $author, 139 | $title = null, 140 | $description = null, 141 | $useVhs = true, 142 | $pages = true, 143 | $content = true, 144 | $controllers = true, 145 | $dry = false, 146 | $verbose = true 147 | ) { 148 | $useVhs = (boolean) $useVhs; 149 | $pages = (boolean) $pages; 150 | $content = (boolean) $content; 151 | $controllers = (boolean) $controllers; 152 | $verbose = (boolean) $verbose; 153 | $dry = (boolean) $dry; 154 | $this->commandService->setResponse($this->response); 155 | $this->commandService->generateProviderExtension( 156 | $extensionKey, 157 | $author, 158 | $title, 159 | $description, 160 | $controllers, 161 | $pages, 162 | $content, 163 | $useVhs, 164 | $dry, 165 | $verbose 166 | ); 167 | } 168 | 169 | /** 170 | * Black hole 171 | * 172 | * @return void 173 | */ 174 | protected function errorCommand() 175 | { 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Resources/Public/Javascript/jqplot.categoryAxisRenderer.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(a){a.jqplot.CategoryAxisRenderer=function(b){a.jqplot.LinearAxisRenderer.call(this);this.sortMergedLabels=false};a.jqplot.CategoryAxisRenderer.prototype=new a.jqplot.LinearAxisRenderer();a.jqplot.CategoryAxisRenderer.prototype.constructor=a.jqplot.CategoryAxisRenderer;a.jqplot.CategoryAxisRenderer.prototype.init=function(e){this.groups=1;this.groupLabels=[];this._groupLabels=[];this._grouped=false;this._barsPerGroup=null;this.reverse=false;a.extend(true,this,{tickOptions:{formatString:"%d"}},e);var b=this._dataBounds;for(var f=0;fb.max||b.max==null){b.max=h[c][0]}}else{if(h[c][1]b.max||b.max==null){b.max=h[c][1]}}}}if(this.groupLabels.length){this.groups=this.groupLabels.length}};a.jqplot.CategoryAxisRenderer.prototype.createTicks=function(){var D=this._ticks;var z=this.ticks;var F=this.name;var C=this._dataBounds;var v,A;var q,w;var d,c;var b,x;if(z.length){if(this.groups>1&&!this._grouped){var r=z.length;var p=parseInt(r/this.groups,10);var e=0;for(var x=p;x1&&!this._grouped){var r=y.length;var p=parseInt(r/this.groups,10);var e=0;for(var x=p;x0&&o');if(this.name=="xaxis"||this.name=="x2axis"){this._elem.width(this._plotDimensions.width)}else{this._elem.height(this._plotDimensions.height)}this.labelOptions.axis=this.name;this._label=new this.labelRenderer(this.labelOptions);if(this._label.show){var g=this._label.draw(b,j);g.appendTo(this._elem)}var f=this._ticks;for(var e=0;e');g.html(this.groupLabels[e]);this._groupLabels.push(g);g.appendTo(this._elem)}}return this._elem};a.jqplot.CategoryAxisRenderer.prototype.set=function(){var e=0;var m;var k=0;var f=0;var d=(this._label==null)?false:this._label.show;if(this.show){var n=this._ticks;for(var c=0;ce){e=m}}}var j=0;for(var c=0;cj){j=m}}if(d){k=this._label._elem.outerWidth(true);f=this._label._elem.outerHeight(true)}if(this.name=="xaxis"){e+=j+f;this._elem.css({height:e+"px",left:"0px",bottom:"0px"})}else{if(this.name=="x2axis"){e+=j+f;this._elem.css({height:e+"px",left:"0px",top:"0px"})}else{if(this.name=="yaxis"){e+=j+k;this._elem.css({width:e+"px",left:"0px",top:"0px"});if(d&&this._label.constructor==a.jqplot.AxisLabelRenderer){this._label._elem.css("width",k+"px")}}else{e+=j+k;this._elem.css({width:e+"px",right:"0px",top:"0px"});if(d&&this._label.constructor==a.jqplot.AxisLabelRenderer){this._label._elem.css("width",k+"px")}}}}}};a.jqplot.CategoryAxisRenderer.prototype.pack=function(e,c){var C=this._ticks;var v=this.max;var s=this.min;var n=c.max;var l=c.min;var q=(this._label==null)?false:this._label.show;var x;for(var r in e){this._elem.css(r,e[r])}this._offsets=c;var g=n-l;var k=v-s;if(!this.reverse){this.u2p=function(h){return(h-s)*g/k+l};this.p2u=function(h){return(h-l)*k/g+s};if(this.name=="xaxis"||this.name=="x2axis"){this.series_u2p=function(h){return(h-s)*g/k};this.series_p2u=function(h){return h*k/g+s}}else{this.series_u2p=function(h){return(h-v)*g/k};this.series_p2u=function(h){return h*k/g+v}}}else{this.u2p=function(h){return l+(v-h)*g/k};this.p2u=function(h){return s+(h-l)*k/g};if(this.name=="xaxis"||this.name=="x2axis"){this.series_u2p=function(h){return(v-h)*g/k};this.series_p2u=function(h){return h*k/g+v}}else{this.series_u2p=function(h){return(s-h)*g/k};this.series_p2u=function(h){return h*k/g+s}}}if(this.show){if(this.name=="xaxis"||this.name=="x2axis"){for(x=0;x=this._ticks.length-1){continue}if(this._ticks[u]._elem&&this._ticks[u].label!=" "){var o=this._ticks[u]._elem;var r=o.position();B+=r.left+o.outerWidth(true)/2;f++}}B=B/f;this._groupLabels[x].css({left:(B-this._groupLabels[x].outerWidth(true)/2)});this._groupLabels[x].css(z[0],z[1])}}else{for(x=0;x0){b=-o._textRenderer.height*Math.cos(-o._textRenderer.angle)/2}else{b=-o.getHeight()+o._textRenderer.height*Math.cos(o._textRenderer.angle)/2}break;case"middle":b=-o.getHeight()/2;break;default:b=-o.getHeight()/2;break}}else{b=-o.getHeight()/2}var D=this.u2p(o.value)+b+"px";o._elem.css("top",D);o.pack()}}var z=["left",0];if(q){var y=this._label._elem.outerHeight(true);this._label._elem.css("top",n-g/2-y/2+"px");if(this.name=="yaxis"){this._label._elem.css("left","0px");z=["left",this._label._elem.outerWidth(true)]}else{this._label._elem.css("right","0px");z=["right",this._label._elem.outerWidth(true)]}this._label.pack()}var d=parseInt(this._ticks.length/this.groups,10)+1;for(x=0;x=this._ticks.length-1){continue}if(this._ticks[u]._elem&&this._ticks[u].label!=" "){var o=this._ticks[u]._elem;var r=o.position();B+=r.top+o.outerHeight()/2;f++}}B=B/f;this._groupLabels[x].css({top:B-this._groupLabels[x].outerHeight()/2});this._groupLabels[x].css(z[0],z[1])}}}}})(jQuery); -------------------------------------------------------------------------------- /Classes/Controller/BackendController.php: -------------------------------------------------------------------------------- 1 | syntaxService = $syntaxService; 39 | } 40 | 41 | /** 42 | * @param ExtensionService $extensionService 43 | * @return void 44 | */ 45 | public function injectExtensionService(ExtensionService $extensionService) 46 | { 47 | $this->extensionService = $extensionService; 48 | } 49 | 50 | /** 51 | * @param string $view 52 | * @return void 53 | */ 54 | public function indexAction($view = 'Index') 55 | { 56 | $extensions = ExtensionUtility::getAllInstalledFluidEnabledExtensions(); 57 | $selectorOptions = ExtensionUtility::getAllInstalledFluidEnabledExtensionsAsSelectorOptions(); 58 | $formats = [ 59 | 'html' => true, 60 | 'xml' => false, 61 | 'txt' => false, 62 | 'eml' => false, 63 | 'yaml' => false, 64 | 'css' => false, 65 | 'js' => false, 66 | ]; 67 | $this->view->assign('csh', BackendUtility::wrapInHelp('builder', 'modules')); 68 | $this->view->assign('view', $view); 69 | $this->view->assign('extensions', $extensions); 70 | $this->view->assign('extensionSelectorOptions', $selectorOptions); 71 | $this->view->assign('formats', $formats); 72 | } 73 | 74 | /** 75 | * @param string $view 76 | * @return void 77 | */ 78 | public function buildFormAction($view = 'BuildForm') 79 | { 80 | $author = ''; 81 | if (!empty($GLOBALS['BE_USER']->user['realName']) && !empty($GLOBALS['BE_USER']->user['email'])) { 82 | $author = $GLOBALS['BE_USER']->user['realName'] . ' <' . $GLOBALS['BE_USER']->user['email'] . '>'; 83 | } 84 | $this->view->assign('csh', BackendUtility::wrapInHelp('builder', 'modules')); 85 | $this->view->assign('view', $view); 86 | $this->view->assign('author', $author); 87 | $isFluidcontentCoreInstalled = ExtensionManagementUtility::isLoaded('fluidcontent_core') ? "'checked'" : null; 88 | $this->view->assign('isFluidcontentCoreInstalled', $isFluidcontentCoreInstalled); 89 | } 90 | 91 | /** 92 | * @param string $name 93 | * @param string $author 94 | * @param string $title 95 | * @param string $description 96 | * @param boolean $controllers 97 | * @param boolean $pages 98 | * @param boolean $content 99 | * @param boolean $vhs 100 | * @param boolean $dry 101 | * @param boolean $verbose 102 | * @return void 103 | */ 104 | public function buildAction( 105 | $name, 106 | $author, 107 | $title, 108 | $description, 109 | $controllers, 110 | $pages, 111 | $content, 112 | $vhs, 113 | $dry, 114 | $verbose 115 | ) { 116 | $generator = $this->extensionService->buildProviderExtensionGenerator( 117 | $name, 118 | $author, 119 | $title, 120 | $description, 121 | $controllers, 122 | $pages, 123 | $content, 124 | $vhs 125 | ); 126 | $generator->setVerbose($verbose); 127 | $generator->setDry($dry); 128 | if (false === $dry) { 129 | $generator->generate(); 130 | } 131 | $this->view->assign('boolean', true); 132 | $this->view->assign('view', 'BuildForm'); 133 | $this->view->assign('attributes', $this->arguments->getArrayCopy()); 134 | } 135 | 136 | /** 137 | * @param array $syntax 138 | * @param array $extensions 139 | * @param array $formats 140 | * @param array $filteredFiles 141 | */ 142 | public function syntaxAction(array $syntax, array $extensions, array $formats, array $filteredFiles = []) 143 | { 144 | if (class_exists(Environment::class)) { 145 | $resourcePath = substr(ExtensionManagementUtility::extPath('builder', 'Resources/Public/'), strlen(Environment::getPublicPath())); 146 | } else { 147 | /** @var DocumentTemplate $document */ 148 | $document = &$GLOBALS['TBE_TEMPLATE']; 149 | $resourcePath = $document->backPath . ExtensionManagementUtility::extRelPath('builder') . 'Resources/Public/'; 150 | } 151 | $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); 152 | $pageRenderer->addJsFile($resourcePath . 'Javascript/jqplot.min.js'); 153 | $pageRenderer->addJsFile($resourcePath . 'Javascript/jqplot.canvasTextRenderer.min.js'); 154 | $pageRenderer->addJsFile($resourcePath . 'Javascript/jqplot.canvasAxisTickRenderer.min.js'); 155 | $pageRenderer->addJsFile($resourcePath . 'Javascript/jqplot.cursor.min.js'); 156 | $pageRenderer->addJsFile($resourcePath . 'Javascript/jqplot.categoryAxisRenderer.min.js'); 157 | $pageRenderer->addJsFile($resourcePath . 'Javascript/jqplot.barRenderer.min.js'); 158 | $pageRenderer->addJsFile($resourcePath . 'Javascript/jqplot.pointLabels.min.js'); 159 | $pageRenderer->addJsFile($resourcePath . 'Javascript/plotter.js'); 160 | $pageRenderer->addCssFile($resourcePath . 'Stylesheet/analysis.css'); 161 | $pageRenderer->addCssFile($resourcePath . 'Stylesheet/jqplot.min.css'); 162 | $pageRenderer->addCssFile($resourcePath . 'Stylesheet/plotter.css'); 163 | $reports = []; 164 | $csvFormats = trim(implode(',', $formats), ','); 165 | foreach ($extensions as $extensionKey) { 166 | if (true === empty($extensionKey)) { 167 | continue; 168 | } 169 | $extensionFolder = realpath(ExtensionManagementUtility::extPath($extensionKey)); 170 | $reports[$extensionKey] = []; 171 | foreach ($syntax as $syntaxName) { 172 | if (true === empty($syntaxName)) { 173 | continue; 174 | } 175 | $reportsForSyntaxName = []; 176 | if ('php' === $syntaxName) { 177 | $reportsForSyntaxName = $this->syntaxService->syntaxCheckPhpFilesInPath( 178 | $extensionFolder . '/Classes' 179 | ); 180 | } elseif ('profile' === $syntaxName) { 181 | $files = GlobUtility::getFilesRecursive($extensionFolder, $csvFormats); 182 | if (0 === count($filteredFiles)) { 183 | $filteredFiles = $files; 184 | } 185 | $this->view->assign('files', $files); 186 | $this->view->assign('basePathLength', strlen($extensionFolder)); 187 | foreach ($files as $file) { 188 | if (0 < count($filteredFiles) && false === in_array($file, $filteredFiles)) { 189 | continue; 190 | } 191 | /** @var TemplateAnalyzer $templateAnalyzer */ 192 | $templateAnalyzer = $this->objectManager->get(TemplateAnalyzer::class); 193 | $shortFilename = substr($file, strpos($file, '/' . $extensionKey . '/') + strlen($extensionKey) + 2); 194 | $reportsForSyntaxName[$shortFilename] = $templateAnalyzer->analyzePathAndFilename($file); 195 | } 196 | $reports[$extensionKey][$syntaxName]['json'] = $this->encodeMetricsToJson($reportsForSyntaxName); 197 | } 198 | $reports[$extensionKey][$syntaxName]['reports'] = $reportsForSyntaxName; 199 | $reports[$extensionKey][$syntaxName]['errors'] = $this->syntaxService->countErrorsInResultCollection( 200 | $reportsForSyntaxName 201 | ); 202 | } 203 | } 204 | $this->view->assign('filteredFiles', $filteredFiles); 205 | $this->view->assign('reports', $reports); 206 | $this->view->assign('extensions', $extensions); 207 | $this->view->assign('formats', $formats); 208 | $this->view->assign('syntax', $syntax); 209 | $this->view->assign('view', 'Index'); 210 | } 211 | 212 | /** 213 | * @param ParserResult[] $metrics 214 | * @return string 215 | */ 216 | protected function encodeMetricsToJson($metrics) 217 | { 218 | foreach ($metrics as $index => $metric) { 219 | $values = $metric->getPayload(); 220 | $metrics[$index] = []; 221 | /** @var Metric $value */ 222 | foreach ($values as $value) { 223 | $metrics[$index][$value->getName()] = $value->getValue(); 224 | } 225 | } 226 | return json_encode($metrics); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Classes/Service/ExtensionService.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 30 | } 31 | 32 | /** 33 | * @param string $extensionKey Extension key, VendorName.ExtensionName format. 34 | * @param string $author Author, format "Name Lastname " 35 | * @param string $title Title of extension (NULL for auto title) 36 | * @param string $description 37 | * @param boolean $controllers Generate controllers for each FluidTYPO3 feature 38 | * @param boolean $pages Include use of fluidpages 39 | * @param boolean $content Include use of fluidcontent 40 | * @param boolean $backend Include use of fluidbackend 41 | * @param boolean $useVhs Include VHS as dependency 42 | * @param boolean $useFluidcontentCore Include FluidcontentCore as dependency 43 | * @return ExtensionGenerator 44 | */ 45 | public function buildProviderExtensionGenerator( 46 | $extensionKey, 47 | $author, 48 | $title = null, 49 | $description = null, 50 | $controllers = false, 51 | $pages = true, 52 | $content = true, 53 | $useVhs = true 54 | ) { 55 | if (!preg_match('/[a-zA-Z]+\\.[a-zA-Z]/', $extensionKey)) { 56 | throw new \InvalidArgumentException('Extension identity must be in VendorName.ExtensionName format', 1601125275); 57 | } 58 | $defaultTitle = 'Provider extension for ' . 59 | (true === $pages ? 'pages ' : '') . 60 | (true === $content ? 'content ' : ''); 61 | ; 62 | if (null === $title) { 63 | $title = $defaultTitle; 64 | } 65 | if (null === $description) { 66 | $description = $defaultTitle; 67 | } 68 | $dependencies = []; 69 | if (true === $useVhs) { 70 | array_push($dependencies, 'vhs'); 71 | } 72 | $dependenciesArrayString = ''; 73 | foreach ($dependencies as $dependency) { 74 | $dependenciesArrayString .= "\n\t\t\t'" . $dependency . "' => '',"; 75 | } 76 | list ($nameAndEmail, $companyName) = GeneralUtility::trimExplode(',', $author); 77 | list ($name, $email) = GeneralUtility::trimExplode('<', $nameAndEmail); 78 | $email = trim($email, '>'); 79 | $name = addslashes($name); 80 | $companyName = addslashes($companyName); 81 | $description = addslashes($description); 82 | $title = addslashes($title); 83 | $baseVersion = substr(TYPO3_version, 0, strrpos(TYPO3_version, '.')); 84 | $minimumVersion = $baseVersion . '.0'; 85 | $maximumVersion = $baseVersion . '.99'; 86 | $extensionVariables = [ 87 | 'extensionName' => $extensionKey, 88 | 'extensionNamespace' => str_replace('.', '\\', $extensionKey) . '\\', 89 | 'extensionKey' => GeneralUtility::camelCaseToLowerCaseUnderscored(substr($extensionKey, strpos($extensionKey, '.') + 1)), 90 | 'title' => $title, 91 | 'description' => $description, 92 | 'date' => date('d-m-Y H:i'), 93 | 'author' => $name, 94 | 'email' => $email, 95 | 'company' => $companyName, 96 | 'coreMinor' => $minimumVersion, 97 | 'coreMajor' => $maximumVersion, 98 | 'controllers' => $controllers, 99 | 'content' => $content, 100 | 'pages' => $pages, 101 | 'dependencies' => $dependencies, 102 | 'dependenciesCsv' => 0 === count($dependencies) ? '' : ',' . implode(',', $dependencies), 103 | 'dependenciesArray' => $dependenciesArrayString 104 | ]; 105 | /** @var ExtensionGenerator $extensionGenerator */ 106 | $extensionGenerator = $this->objectManager->get(ExtensionGenerator::class); 107 | $extensionGenerator->setConfiguration($extensionVariables); 108 | return $extensionGenerator; 109 | } 110 | 111 | /** 112 | * Prints information according to the instances properties 113 | * 114 | * @param string $format Desired format. Currently 'text' or 'json' 115 | * @param boolean $detail Detailed Output? Mandatory if $format == 'json' 116 | * @param integer $state Extension state 117 | * 118 | * @return string 119 | */ 120 | public function getPrintableInformation($format = 'text', $detail = false, $state = self::STATE_ALL) 121 | { 122 | $extensionInformation = $this->gatherInformation(); 123 | 124 | if ('text' === $format) { 125 | $printedInfo = $this->printAsText($extensionInformation, $detail, $state); 126 | return $printedInfo; 127 | } elseif ('json' === $format) { 128 | $printedJsonInfo = $this->printAsJson($extensionInformation); 129 | return $printedJsonInfo; 130 | } else { 131 | return ''; 132 | } 133 | } 134 | 135 | /** 136 | * Returns an array of extension information. Which is the same as the --detail switch. 137 | * 138 | * A facade is used as more behaviour might be hidden. 139 | * 140 | * @return array 141 | */ 142 | public function getComputableInformation() 143 | { 144 | $extensionInformation = $this->gatherInformation(); 145 | 146 | return $extensionInformation; 147 | } 148 | 149 | /** 150 | * Gathers Extension Information 151 | * 152 | * @return array 153 | */ 154 | private function gatherInformation() 155 | { 156 | /** @var ListUtility $service */ 157 | $service = $this->objectManager->get('TYPO3\CMS\Extensionmanager\Utility\ListUtility'); 158 | 159 | $extensionInformation = $service->getAvailableExtensions(); 160 | foreach ($extensionInformation as $extensionKey => $info) { 161 | $extensionInformation[$extensionKey]['version'] = ExtensionManagementUtility::getExtensionVersion( 162 | $extensionKey 163 | ); 164 | if (ExtensionManagementUtility::isLoaded($extensionKey)) { 165 | $extensionInformation[$extensionKey]['installed'] = 1; 166 | } else { 167 | $extensionInformation[$extensionKey]['installed'] = 0; 168 | } 169 | } 170 | return $extensionInformation; 171 | } 172 | 173 | /** 174 | * Prints text-output 175 | * 176 | * @param array $extensionInformation 177 | * @param boolean $detail 178 | * @param int $state 179 | * 180 | * @return string 181 | */ 182 | private function printAsText($extensionInformation, $detail = false, $state = self::STATE_ALL) 183 | { 184 | $output = ''; 185 | foreach ($extensionInformation as $extension => $info) { 186 | if (false === $detail) { 187 | switch ($state) { 188 | case self::STATE_INACTIVE: 189 | if (0 === intval($info['installed'])) { 190 | $output .= $extension . LF; 191 | } 192 | break; 193 | case self::STATE_ACTIVE: 194 | if (1 === intval($info['installed'])) { 195 | $output .= $extension . LF; 196 | } 197 | break; 198 | default: 199 | $output .= $extension . LF; 200 | break; 201 | } 202 | } elseif (true === $detail) { 203 | switch ($state) { 204 | case self::STATE_INACTIVE: 205 | if (0 === intval($info['installed'])) { 206 | $output .= $this->concatStringOutput($extension, $info); 207 | } 208 | break; 209 | case self::STATE_ACTIVE: 210 | if (1 === intval($info['installed'])) { 211 | $output .= $this->concatStringOutput($extension, $info); 212 | } 213 | break; 214 | default: 215 | $output .= $this->concatStringOutput($extension, $info); 216 | break; 217 | } 218 | } 219 | } 220 | return $output; 221 | } 222 | 223 | /** 224 | * Concats the detailed output to yaml 225 | * 226 | * @param string $extension Extension Name 227 | * @param array $info Extension Information 228 | * @return string 229 | */ 230 | private function concatStringOutput($extension, $info) 231 | { 232 | return $extension . ':' . LF . 233 | ' version: ' . $info['version'] . LF . 234 | ' installed: ' . $info['installed'] . LF . 235 | ' type: ' . $info['type'] . LF . 236 | ' path: ' . $info['siteRelPath'] . 237 | LF; 238 | } 239 | 240 | /** 241 | * @param array $extensionInformation 242 | * 243 | * @return string 244 | */ 245 | public function printAsJson($extensionInformation) 246 | { 247 | $json = json_encode($extensionInformation) . LF; 248 | 249 | return $json; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /Resources/Public/Javascript/jqplot.barRenderer.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(d){d.jqplot.BarRenderer=function(){d.jqplot.LineRenderer.call(this)};d.jqplot.BarRenderer.prototype=new d.jqplot.LineRenderer();d.jqplot.BarRenderer.prototype.constructor=d.jqplot.BarRenderer;d.jqplot.BarRenderer.prototype.init=function(o,q){this.barPadding=8;this.barMargin=10;this.barDirection="vertical";this.barWidth=null;this.shadowOffset=2;this.shadowDepth=5;this.shadowAlpha=0.08;this.waterfall=false;this.groups=1;this.varyBarColor=false;this.highlightMouseOver=true;this.highlightMouseDown=false;this.highlightColors=[];this.transposedData=true;this.renderer.animation={show:false,direction:"down",speed:3000,_supported:true};this._type="bar";if(o.highlightMouseDown&&o.highlightMouseOver==null){o.highlightMouseOver=false}d.extend(true,this,o);d.extend(true,this.renderer,o);this.fill=true;if(this.barDirection==="horizontal"&&this.rendererOptions.animation&&this.rendererOptions.animation.direction==null){this.renderer.animation.direction="left"}if(this.waterfall){this.fillToZero=false;this.disableStack=true}if(this.barDirection=="vertical"){this._primaryAxis="_xaxis";this._stackAxis="y";this.fillAxis="y"}else{this._primaryAxis="_yaxis";this._stackAxis="x";this.fillAxis="x"}this._highlightedPoint=null;this._plotSeriesInfo=null;this._dataColors=[];this._barPoints=[];var p={lineJoin:"miter",lineCap:"round",fill:true,isarc:false,strokeStyle:this.color,fillStyle:this.color,closePath:this.fill};this.renderer.shapeRenderer.init(p);var n={lineJoin:"miter",lineCap:"round",fill:true,isarc:false,angle:this.shadowAngle,offset:this.shadowOffset,alpha:this.shadowAlpha,depth:this.shadowDepth,closePath:this.fill};this.renderer.shadowRenderer.init(n);q.postInitHooks.addOnce(h);q.postDrawHooks.addOnce(j);q.eventListenerHooks.addOnce("jqplotMouseMove",b);q.eventListenerHooks.addOnce("jqplotMouseDown",a);q.eventListenerHooks.addOnce("jqplotMouseUp",l);q.eventListenerHooks.addOnce("jqplotClick",e);q.eventListenerHooks.addOnce("jqplotRightClick",m)};function g(t,p,o,w){if(this.rendererOptions.barDirection=="horizontal"){this._stackAxis="x";this._primaryAxis="_yaxis"}if(this.rendererOptions.waterfall==true){this._data=d.extend(true,[],this.data);var s=0;var u=(!this.rendererOptions.barDirection||this.rendererOptions.barDirection==="vertical"||this.transposedData===false)?1:0;for(var q=0;q0){this.data[q][u]+=this.data[q-1][u]}}this.data[this.data.length]=(u==1)?[this.data.length+1,s]:[s,this.data.length+1];this._data[this._data.length]=(u==1)?[this._data.length+1,s]:[s,this._data.length+1]}if(this.rendererOptions.groups>1){this.breakOnNull=true;var n=this.data.length;var v=parseInt(n/this.rendererOptions.groups,10);var r=0;for(var q=v;q570)?n[p]*0.8:n[p]+0.3*(255-n[p]);n[p]=parseInt(n[p],10)}q.push("rgb("+n[0]+","+n[1]+","+n[2]+")")}return q}function i(v,u,s,t,o){var q=v,w=v-1,n,p,r=(o==="x")?0:1;if(q>0){p=t.series[w]._plotData[u][r];if((s*p)<0){n=i(w,u,s,t,o)}else{n=t.series[w].gridData[u][r]}}else{n=(r===0)?t.series[q]._xaxis.series_u2p(0):t.series[q]._yaxis.series_u2p(0)}return n}d.jqplot.BarRenderer.prototype.draw=function(E,L,q,G){var I;var A=d.extend({},q);var w=(A.shadow!=undefined)?A.shadow:this.shadow;var O=(A.showLine!=undefined)?A.showLine:this.showLine;var F=(A.fill!=undefined)?A.fill:this.fill;var p=this.xaxis;var J=this.yaxis;var y=this._xaxis.series_u2p;var K=this._yaxis.series_u2p;var D,C;this._dataColors=[];this._barPoints=[];if(this.barWidth==null){this.renderer.setBarWidth.call(this)}var N=this._plotSeriesInfo=this.renderer.calcSeriesNumbers.call(this);var x=N[0];var v=N[1];var s=N[2];var H=[];if(this._stack){this._barNudge=0}else{this._barNudge=(-Math.abs(v/2-0.5)+s)*(this.barWidth+this.barPadding)}if(O){var u=new d.jqplot.ColorGenerator(this.negativeSeriesColors);var B=new d.jqplot.ColorGenerator(this.seriesColors);var M=u.get(this.index);if(!this.useNegativeColors){M=A.fillStyle}var t=A.fillStyle;var r;var P;var o;if(this.barDirection=="vertical"){for(var I=0;I0&&I=0){o=this._yaxis.series_u2p(0)}else{if(this._yaxis.min>0){o=E.canvas.height}else{o=0}}}else{if(this.waterfall&&I==this.gridData.length-1){if(this._yaxis.min<=0&&this._yaxis.max>=0){o=this._yaxis.series_u2p(0)}else{if(this._yaxis.min>0){o=E.canvas.height}else{o=0}}}else{o=E.canvas.height}}}}}if((this.fillToZero&&this._plotData[I][1]<0)||(this.waterfall&&this._data[I][1]<0)){if(this.varyBarColor&&!this._stack){if(this.useNegativeColors){A.fillStyle=u.next()}else{A.fillStyle=B.next()}}else{A.fillStyle=M}}else{if(this.varyBarColor&&!this._stack){A.fillStyle=B.next()}else{A.fillStyle=t}}if(!this.fillToZero||this._plotData[I][1]>=0){H.push([r-this.barWidth/2,o]);H.push([r-this.barWidth/2,L[I][1]]);H.push([r+this.barWidth/2,L[I][1]]);H.push([r+this.barWidth/2,o])}else{H.push([r-this.barWidth/2,L[I][1]]);H.push([r-this.barWidth/2,o]);H.push([r+this.barWidth/2,o]);H.push([r+this.barWidth/2,L[I][1]])}this._barPoints.push(H);if(w&&!this._stack){var z=d.extend(true,{},A);delete z.fillStyle;this.renderer.shadowRenderer.draw(E,H,z)}var n=A.fillStyle||this.color;this._dataColors.push(n);this.renderer.shapeRenderer.draw(E,H,A)}}else{if(this.barDirection=="horizontal"){for(var I=0;I0&&I=0){P=this._xaxis.series_u2p(0)}else{if(this._xaxis.min>0){P=0}else{P=0}}}else{if(this.waterfall&&I==this.gridData.length-1){if(this._xaxis.min<=0&&this._xaxis.max>=0){P=this._xaxis.series_u2p(0)}else{if(this._xaxis.min>0){P=0}else{P=E.canvas.width}}}else{P=0}}}}}if((this.fillToZero&&this._plotData[I][0]<0)||(this.waterfall&&this._data[I][0]<0)){if(this.varyBarColor&&!this._stack){if(this.useNegativeColors){A.fillStyle=u.next()}else{A.fillStyle=B.next()}}else{A.fillStyle=M}}else{if(this.varyBarColor&&!this._stack){A.fillStyle=B.next()}else{A.fillStyle=t}}if(!this.fillToZero||this._plotData[I][0]>=0){H.push([P,r+this.barWidth/2]);H.push([P,r-this.barWidth/2]);H.push([L[I][0],r-this.barWidth/2]);H.push([L[I][0],r+this.barWidth/2])}else{H.push([L[I][0],r+this.barWidth/2]);H.push([L[I][0],r-this.barWidth/2]);H.push([P,r-this.barWidth/2]);H.push([P,r+this.barWidth/2])}this._barPoints.push(H);if(w&&!this._stack){var z=d.extend(true,{},A);delete z.fillStyle;this.renderer.shadowRenderer.draw(E,H,z)}var n=A.fillStyle||this.color;this._dataColors.push(n);this.renderer.shapeRenderer.draw(E,H,A)}}}}if(this.highlightColors.length==0){this.highlightColors=d.jqplot.computeHighlightColors(this._dataColors)}else{if(typeof(this.highlightColors)=="string"){var N=this.highlightColors;this.highlightColors=[];for(var I=0;IsyntaxService = $syntaxService ?? GeneralUtility::makeInstance(ObjectManager::class)->get(SyntaxService::class); 38 | $this->extensionService = $extensionService ?? GeneralUtility::makeInstance(ObjectManager::class)->get(ExtensionService::class); 39 | } 40 | 41 | /** 42 | * @param Response $response 43 | */ 44 | public function setResponse(Response $response): void 45 | { 46 | $this->response = $response; 47 | } 48 | 49 | /** 50 | * @param OutputInterface $output 51 | */ 52 | public function setOutput(OutputInterface $output): void 53 | { 54 | $this->output = $output; 55 | } 56 | 57 | /** 58 | * Lists installed Extensions. The output defaults to text and is new-line separated. 59 | * 60 | * @param boolean $detail If TRUE, the command will give detailed information such as version and state 61 | * @param boolean $active If TRUE, the command will give information about active extensions only 62 | * @param boolean $inactive If TRUE, the command will give information about inactive extensions only 63 | * @param boolean $json If TRUE, the command will return a json object-string 64 | * @throws \Exception 65 | * @return int 66 | */ 67 | public function listExtensions(bool $detail = false, bool $active = true, bool $inactive = false, bool $json = false): int 68 | { 69 | $format = 'text'; 70 | if (true === $json) { 71 | $format = 'json'; 72 | } 73 | if ($active && $inactive) { 74 | $state = ExtensionService::STATE_ALL; 75 | } elseif ($active && !$inactive) { 76 | $state = ExtensionService::STATE_ACTIVE; 77 | } elseif ($inactive && !$active) { 78 | $state = ExtensionService::STATE_INACTIVE; 79 | } else { 80 | $state = ExtensionService::STATE_ALL; 81 | } 82 | 83 | $this->setContent( 84 | $this->extensionService->getPrintableInformation($format, $detail, $state) 85 | ); 86 | 87 | return 0; 88 | } 89 | 90 | /** 91 | * Syntax check Fluid template 92 | * 93 | * Checks one template file, all templates in 94 | * an extension or a sub-path (which can be used 95 | * with an extension key for a relative path). 96 | * If left out, it will lint ALL templates in 97 | * EVERY local extension. 98 | * 99 | * @param string $extension Optional extension key (if path is set too it will apply to sub-folders in extension) 100 | * @param string $path file or folder path (if extensionKey is included, path is relative to this extension) 101 | * @param string $extensions If provided, this CSV list of file extensions are considered Fluid templates 102 | * @param boolean $verbose If TRUE outputs more information about each file check - default is to only output errors 103 | * @throws \RuntimeException 104 | * @return int 105 | */ 106 | public function checkFluidSyntax(?String $extension = null, ?string $path = null, string $extensions = 'html,xml,txt', bool $verbose = false): int 107 | { 108 | if (null !== $extension) { 109 | $this->assertEitherExtensionKeyOrPathOrBothAreProvidedOrExit($extension, $path); 110 | $path = GlobUtility::getRealPathFromExtensionKeyAndPath($extension, $path); 111 | $files = GlobUtility::getFilesRecursive($path, $extensions); 112 | } else { 113 | // no extension key given, let's lint it all 114 | $files = []; 115 | /** @var ExtensionService $extensionService */ 116 | $extensionInformation = $this->extensionService->getComputableInformation(); 117 | foreach ($extensionInformation as $extensionName => $extensionInfo) { 118 | // Syntax service declines linting of inactive extensions 119 | if (0 === intval($extensionInfo['installed']) || 'System' === $extensionInfo['type']) { 120 | continue; 121 | } 122 | $path = GlobUtility::getRealPathFromExtensionKeyAndPath($extensionName, null); 123 | $files = array_merge($files, GlobUtility::getFilesRecursive($path, $extensions)); 124 | } 125 | } 126 | $files = array_values($files); 127 | $errors = false; 128 | $this->setContent( 129 | 'Performing a syntax check on fluid templates (types: ' . $extensions . '; path: ' . $path . ')' . LF 130 | ); 131 | $this->send(); 132 | foreach ($files as $filePathAndFilename) { 133 | $basePath = str_replace(Environment::getProjectPath(), '', $filePathAndFilename); 134 | $result = $this->syntaxService->syntaxCheckFluidTemplateFile($filePathAndFilename); 135 | if (null !== $result->getError()) { 136 | $this->appendContent('[ERROR] File ' . $basePath . ' has an error: ' . LF); 137 | $this->appendContent( 138 | $result->getError()->getMessage() . ' (' . $result->getError()->getCode() . ')' . LF 139 | ); 140 | $this->send(); 141 | $errors = true; 142 | } elseif (true === $verbose) { 143 | $namespaces = $result->getNamespaces(); 144 | $this->appendContent( 145 | 'File is compilable: ' . ($result->getCompilable() ? 'YES' : 'NO (WARNING)') . LF 146 | ); 147 | if ($result->getLayoutName()) { 148 | $this->appendContent('File has layout (' . $result->getLayoutName() . ')' . LF); 149 | } else { 150 | $this->appendContent('File DOES NOT reference a Layout' . LF); 151 | } 152 | $this->appendContent( 153 | 'File has ' . count($namespaces) . ' namespace(s)' . 154 | (0 < count($namespaces) ? ': ' . $result->getNamespacesFlattened() : '') . LF 155 | ); 156 | } 157 | if (null === $result->getError()) { 158 | $this->appendContent('[OK] File ' . $basePath . ' is valid.' . LF); 159 | $this->send(); 160 | } 161 | } 162 | return $this->stop($files, $errors, $verbose); 163 | } 164 | 165 | /** 166 | * Syntax check PHP code 167 | * 168 | * Checks PHP source files in $path, if extension 169 | * key is also given, only files in that path relative 170 | * to that extension are checked. 171 | * 172 | * @param string $extension Optional extension key (if path is set too it will apply to sub-folders in extension) 173 | * @param string $path file or folder path (if extensionKey is included, path is relative to this extension) 174 | * @param boolean $verbose If TRUE outputs more information about each file check - default is to only output errors 175 | * @return int 176 | */ 177 | public function checkPhpSyntax(?string $extension = null, ?string $path = null, bool $verbose = false): int 178 | { 179 | $verbose = (boolean) $verbose; 180 | $this->assertEitherExtensionKeyOrPathOrBothAreProvidedOrExit($extension, $path); 181 | if (null !== $extension) { 182 | $results = $this->syntaxService->syntaxCheckPhpFilesInExtension($extension); 183 | } else { 184 | $results = $this->syntaxService->syntaxCheckPhpFilesInPath($path); 185 | } 186 | $errors = false; 187 | foreach ($results as $filePathAndFilename => $result) { 188 | $result = $this->syntaxService->syntaxCheckPhpFile($filePathAndFilename); 189 | if (null !== $result->getError()) { 190 | $errors = true; 191 | $this->setContent( 192 | '[ERROR] ' . $result->getError()->getMessage() . ' (' . $result->getError()->getCode() . ')' . LF 193 | ); 194 | } 195 | } 196 | return $this->stop($results, $errors, $verbose); 197 | } 198 | 199 | /** 200 | * Builds a ProviderExtension 201 | * 202 | * The resulting extension will contain source code 203 | * and configuration options needed by the various 204 | * toggles. Each of these toggles enable/disable 205 | * generation of source code and configuration for 206 | * that particular feature. 207 | * 208 | * @param string $extensionKey The extension key which should be generated. Must not exist. 209 | * @param string $author The author of the extension, in the format "Name Lastname " with optional 210 | * company name, in which case form is "Name Lastname , Company Name" 211 | * @param string $title Title of the resulting extension, by default "Provider extension for $enabledFeaturesList" 212 | * @param string $description Description for extension, by default "Provider extension for $enabledFeaturesList" 213 | * @param boolean $useVhs If TRUE, adds the VHS extension as dependency - recommended, on by default 214 | * @param boolean $pages If TRUE, generates basic files for implementing Fluid Page templates 215 | * @param boolean $content IF TRUE, generates basic files for implementing Fluid Content templates 216 | * @param boolean $controllers If TRUE, generates controllers for each enabled feature. Enabling $backend will 217 | * always generate a controller regardless of this toggle. 218 | * @param boolean $dry If TRUE performs a dry run without writing files;reports which files would have been written 219 | * @param boolean $verbose If FALSE, suppresses a lot of the otherwise output messages (to STDOUT) 220 | * @return int 221 | */ 222 | public function generateProviderExtension( 223 | string $extensionKey, 224 | string $author, 225 | ?string $title = null, 226 | ?string $description = null, 227 | bool $useVhs = true, 228 | bool $pages = true, 229 | bool $content = true, 230 | bool $controllers = true, 231 | bool $dry = false, 232 | bool $verbose = true 233 | ):int { 234 | $extensionGenerator = $this->extensionService->buildProviderExtensionGenerator( 235 | $extensionKey, 236 | $author, 237 | $title, 238 | $description, 239 | $controllers, 240 | $pages, 241 | $content, 242 | $useVhs 243 | ); 244 | $extensionGenerator->setDry($dry); 245 | $extensionGenerator->setVerbose($verbose); 246 | $this->setContent($extensionGenerator->generate() . PHP_EOL); 247 | return 0; 248 | } 249 | 250 | private function assertEitherExtensionKeyOrPathOrBothAreProvidedOrExit(?string $extension, ?string $path): void 251 | { 252 | if (null === $extension && null === $path) { 253 | $this->setContent('Either "extension" or "path" or both must be specified' . LF); 254 | $this->send(); 255 | $this->setExitCode(128); 256 | } 257 | } 258 | 259 | private function send(): void 260 | { 261 | if ($this->response instanceof ResponseInterface) { 262 | $this->response->send(); 263 | } 264 | } 265 | 266 | private function setContent(string $content): void 267 | { 268 | if ($this->output instanceof OutputInterface) { 269 | $this->output->write($content); 270 | } elseif ($this->response instanceof ResponseInterface) { 271 | $this->response->setContent($content); 272 | } 273 | } 274 | 275 | private function appendContent(string $content): void 276 | { 277 | if ($this->output instanceof OutputInterface) { 278 | $this->output->write($content); 279 | } elseif ($this->response instanceof ResponseInterface) { 280 | $this->response->appendContent($content); 281 | } 282 | } 283 | 284 | private function setExitCode(int $code): void 285 | { 286 | if ($this->response instanceof ResponseInterface) { 287 | $this->response->setExitCode($code); 288 | } 289 | } 290 | 291 | private function stop(array $files, bool $errors, bool $verbose): int 292 | { 293 | $code = (int) $errors; 294 | if (true === (boolean) $verbose) { 295 | if (false === $errors) { 296 | $this->setContent('No errors encountered - ' . count($files) . ' file(s) are all okay' . LF); 297 | } else { 298 | $this->setContent('Errors were detected - review the summary above' . LF); 299 | $this->setExitCode($code); 300 | } 301 | } 302 | $this->send(); 303 | return $code; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /Classes/CodeGeneration/Extension/ExtensionGenerator.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 42 | } 43 | 44 | /** 45 | * @param string $targetFolder 46 | * @return void 47 | */ 48 | public function setTargetFolder($targetFolder) 49 | { 50 | $this->targetFolder = $targetFolder; 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | protected function getExtensionKeyFromSettings() 57 | { 58 | $extensionKey = $this->configuration['extensionKey']; 59 | if (false !== strpos($extensionKey, '.')) { 60 | $extensionKey = array_pop(explode('.', $extensionKey)); 61 | } 62 | return GeneralUtility::camelCaseToLowerCaseUnderscored($extensionKey); 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | protected function getExtensionNamespaceFromSettings() 69 | { 70 | return $this->configuration['extensionNamespace']; 71 | } 72 | 73 | /** 74 | * @return string 75 | * @throws \RuntimeException 76 | */ 77 | public function generate() 78 | { 79 | $extensionKey = $this->getExtensionKeyFromSettings(); 80 | if (null === $this->targetFolder) { 81 | $this->setTargetFolder((defined('PATH_site') ? constant('PATH_typo3conf') . 'ext/' : Environment::getPublicPath() . '/typo3conf/ext/') . $extensionKey); 82 | } 83 | if (true === is_dir($this->targetFolder)) { 84 | throw new \RuntimeException( 85 | 'Extension key "' . $extensionKey . '" already has a folder in "' . $this->targetFolder . '"', 86 | 1371692599 87 | ); 88 | } 89 | $filesToBeWritten = [ 90 | $this->targetFolder . '/ext_emconf.php' => $this->getPreparedCodeTemplate( 91 | self::TEMPLATE_EMCONF, 92 | $this->configuration 93 | )->render() 94 | ]; 95 | $foldersToBeCreated = [$this->targetFolder]; 96 | $hasVhs = true === in_array('vhs', $this->configuration['dependencies']); 97 | $appendLanguageFile = false; 98 | if ($this->configuration['pages'] ?? false) { 99 | $this->appendPageFiles($filesToBeWritten); 100 | $appendLanguageFile = true; 101 | } 102 | if ($this->configuration['content'] ?? false) { 103 | $this->appendContentFiles($filesToBeWritten, $hasVhs); 104 | $appendLanguageFile = true; 105 | } 106 | if ($appendLanguageFile) { 107 | $this->appendLanguageFile($filesToBeWritten); 108 | } 109 | $controllerFolder = $this->targetFolder . '/Classes/Controller/'; 110 | if (true === $this->configuration['controllers']) { 111 | array_push($foldersToBeCreated, $controllerFolder); 112 | } 113 | if (true === $this->configuration['controllers']) { 114 | if ($this->configuration['content'] ?? false) { 115 | $this->appendControllerClassFile( 116 | $filesToBeWritten, 117 | 'Content', 118 | ContentController::class, 119 | $controllerFolder 120 | ); 121 | } 122 | if ($this->configuration['pages'] ?? false) { 123 | $this->appendControllerClassFile( 124 | $filesToBeWritten, 125 | 'Page', 126 | PageController::class, 127 | $controllerFolder 128 | ); 129 | } 130 | } 131 | $this->appendTypoScriptConfiguration($filesToBeWritten); 132 | $this->appendExtensionLocalconfFile($filesToBeWritten); 133 | $this->appendTypoScriptIntegrationFile($filesToBeWritten); 134 | $foldersToBeCreated = array_unique($foldersToBeCreated); 135 | foreach ($foldersToBeCreated as $folderPathToBeCreated) { 136 | $this->createFolder($folderPathToBeCreated); 137 | } 138 | foreach ($filesToBeWritten as $fileToBeWritten => $fileContentToBeWritten) { 139 | $this->createFile($fileToBeWritten, $fileContentToBeWritten); 140 | } 141 | if ($this->configuration['pages'] ?? false) { 142 | $this->copyFile( 143 | 'Resources/Public/Icons/Example.svg', 144 | $this->targetFolder . '/Resources/Public/Icons/Page/Standard.svg' 145 | ); 146 | } 147 | if ($this->configuration['content'] ?? false) { 148 | $this->copyFile( 149 | 'Resources/Public/Icons/Example.svg', 150 | $this->targetFolder . '/Resources/Public/Icons/Content/Example.svg' 151 | ); 152 | } 153 | $this->copyFile( 154 | 'Resources/Public/Icons/Example.svg', 155 | $this->targetFolder . '/Resources/Public/Icons/Extension.svg' 156 | ); 157 | return 'Built extension "' . $extensionKey . '"'; 158 | } 159 | 160 | /** 161 | * @param array $files 162 | * @param string $controllerName 163 | * @param string $parentControllerClassName 164 | * @param string $folder 165 | */ 166 | protected function appendControllerClassFile(&$files, $controllerName, $parentControllerClassName, $folder) 167 | { 168 | $templateVariables = $this->configuration; 169 | $templateVariables['controllerName'] = $controllerName; 170 | $templateVariables['parentControllerClass'] = $parentControllerClassName; 171 | $templateVariables['namespace'] = $this->getExtensionNamespaceFromSettings() . 'Controller'; 172 | $files[$folder . $controllerName . 'Controller.php'] = $this->getPreparedCodeTemplate( 173 | self::TEMPLATE_CONTROLLER, 174 | $templateVariables 175 | )->render(); 176 | } 177 | 178 | /** 179 | * @param array $files 180 | * @return void 181 | */ 182 | protected function appendTypoScriptConfiguration(&$files) 183 | { 184 | $extensionKey = $this->getExtensionKeyFromSettings(); 185 | $templateVariables = [ 186 | 'extension' => $extensionKey, 187 | 'signature' => ExtensionManagementUtility::getCN($extensionKey) 188 | ]; 189 | $folder = $this->targetFolder . '/Configuration/TypoScript'; 190 | $files[$folder . '/constants.typoscript'] = $this->getPreparedCodeTemplate( 191 | self::TEMPLATE_TYPOSCRIPTCONSTANTS, 192 | $templateVariables 193 | )->render(); 194 | $files[$folder . '/setup.typoscript'] = $this->getPreparedCodeTemplate( 195 | self::TEMPLATE_TYPOSCRIPTSETUP, 196 | $templateVariables 197 | )->render(); 198 | } 199 | 200 | /** 201 | * @param array $files 202 | * @return void 203 | */ 204 | protected function appendExtensionLocalconfFile(&$files) 205 | { 206 | $templateVariables = [ 207 | 'pages' => '', 208 | 'content' => '', 209 | 'configuration' => '', 210 | ]; 211 | 212 | // note: the following code uses the provided "extensionKey" *directly* because 213 | // for these registrations, we require the full Vendor.ExtensionName if that 214 | // is the format used. Otherwise, legacy class names would be expected. 215 | if ($this->configuration['pages'] ?? false) { 216 | $templateVariables['pages'] = '\FluidTYPO3\Flux\Core::registerProviderExtensionKey(\'' . 217 | $this->configuration['extensionName'] . '\', \'Page\');'; 218 | } 219 | if ($this->configuration['content'] ?? false) { 220 | $templateVariables['content'] = '\FluidTYPO3\Flux\Core::registerProviderExtensionKey(\'' . 221 | $this->configuration['extensionName'] . '\', \'Content\');'; 222 | } 223 | $files[$this->targetFolder . '/ext_localconf.php'] = $this->getPreparedCodeTemplate( 224 | self::TEMPLATE_EXTLOCALCONF, 225 | $templateVariables 226 | )->render(); 227 | } 228 | 229 | /** 230 | * @param array $files 231 | * @return void 232 | */ 233 | protected function appendTypoScriptIntegrationFile(&$files) 234 | { 235 | $title = trim($this->configuration['title']); 236 | $templateVariables = [ 237 | 'pages' => '', 238 | 'content' => '', 239 | 'configuration' => sprintf( 240 | '\\%s::addStaticFile(\'%s\', \'Configuration/TypoScript\', \'%s\');', 241 | ExtensionManagementUtility::class, 242 | $this->configuration['extensionKey'], 243 | $title 244 | ) 245 | ]; 246 | 247 | $files[$this->targetFolder . '/Configuration/TCA/Overrides/sys_template.php'] = $this->getPreparedCodeTemplate( 248 | self::TEMPLATE_EXTLOCALCONF, 249 | $templateVariables 250 | )->render(); 251 | } 252 | 253 | /** 254 | * @param array $files 255 | * @return void 256 | */ 257 | protected function appendLanguageFile(&$files) 258 | { 259 | $variables = [ 260 | 'extension' => $this->getExtensionKeyFromSettings(), 261 | 'date' => date('c') 262 | ]; 263 | $filePathAndFilename = $this->targetFolder . '/Resources/Private/Language/locallang.xlf'; 264 | $files[$filePathAndFilename] = $this->getPreparedCodeTemplate( 265 | self::TEMPLATE_LANGUAGEFILE, 266 | $variables 267 | )->render(); 268 | } 269 | 270 | /** 271 | * @param array $files 272 | * @param boolean $hasVhs 273 | * @return void 274 | */ 275 | protected function appendContentFiles(&$files, $hasVhs) 276 | { 277 | $layoutName = 'Content'; 278 | $sectionName = 'Main'; 279 | $variables = [ 280 | 'formId' => 'example' 281 | ]; 282 | $this->appendLayoutFile($files, $layoutName); 283 | if (true === $hasVhs) { 284 | $variables['vhs'] = 'xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers"'; 285 | $layoutName = 'Content'; 286 | } 287 | $this->appendTemplateFile( 288 | $files, 289 | self::TEMPLATE_CONTENT, 290 | $layoutName, 291 | $sectionName, 292 | 'Content/Example.html', 293 | $variables 294 | ); 295 | } 296 | 297 | /** 298 | * @param array $files 299 | * @return void 300 | */ 301 | protected function appendPageFiles(&$files) 302 | { 303 | $layoutName = 'Page'; 304 | $sectionName = 'Main'; 305 | $variables = [ 306 | 'formId' => 'standard' 307 | ]; 308 | $this->appendLayoutFile($files, $layoutName); 309 | $this->appendTemplateFile( 310 | $files, 311 | self::TEMPLATE_PAGE, 312 | $layoutName, 313 | $sectionName, 314 | 'Page/Standard.html', 315 | $variables 316 | ); 317 | } 318 | 319 | /** 320 | * @param array $files 321 | * @param string $identifier 322 | * @param string $layout 323 | * @param string $section 324 | * @param string $placement 325 | * @param array $variables 326 | * @return void 327 | */ 328 | protected function appendTemplateFile(&$files, $identifier, $layout, $section, $placement, array $variables) 329 | { 330 | $templateVariables = [ 331 | 'layout' => $layout, 332 | 'section' => $section, 333 | 'configurationSectionName' => 'Configuration', 334 | 'id' => str_replace('/', '', strtolower($identifier)), 335 | 'label' => $identifier, 336 | 'icon' => 'Icons/' . substr($placement, 0, -4) . 'gif', 337 | 'extension' => $this->getExtensionKeyFromSettings(), 338 | 'placement' => $placement 339 | ]; 340 | $templateVariables = array_merge_recursive($variables, $templateVariables); 341 | $templatePathAndFilename = $this->targetFolder . '/Resources/Private/Templates/' . $placement; 342 | $files[$templatePathAndFilename] = $this->getPreparedCodeTemplate($identifier, $templateVariables)->render(); 343 | } 344 | 345 | /** 346 | * @param array $files 347 | * @param string $layoutName 348 | * @param string $layoutSectionRenderName 349 | * @param string $layoutType 350 | */ 351 | protected function appendLayoutFile( 352 | &$files, 353 | $layoutName, 354 | $layoutSectionRenderName = 'Main', 355 | $layoutType = self::TEMPLATE_LAYOUT 356 | ) { 357 | $layoutVariables = [ 358 | 'name' => $layoutName, 359 | 'section' => $layoutSectionRenderName 360 | ]; 361 | $layoutPathAndFilename = $this->targetFolder . '/Resources/Private/Layouts/' . $layoutName . '.html'; 362 | $files[$layoutPathAndFilename] = $this->getPreparedCodeTemplate($layoutType, $layoutVariables)->render(); 363 | } 364 | } 365 | --------------------------------------------------------------------------------