├── skeleton ├── public │ ├── css │ │ ├── styles.min.css │ │ └── styles.css │ └── js │ │ └── script.js ├── contao │ ├── templates │ │ └── twig │ │ │ ├── .twig-root │ │ │ ├── content_element │ │ │ └── sample_element.html.twig.ttpl │ │ │ └── frontend_module │ │ │ └── sample_module.html.twig.ttpl │ ├── languages │ │ └── en │ │ │ ├── tl_module.php.ttpl │ │ │ ├── default.php.ttpl │ │ │ ├── modules.php.ttpl │ │ │ └── tl_sample_table.php.ttpl │ ├── dca │ │ ├── tl_module.php.ttpl │ │ ├── tl_content.php.ttpl │ │ └── tl_sample_table.php.ttpl │ └── config │ │ └── config.php.ttpl ├── config │ ├── listener.yaml.ttpl │ ├── parameters.yaml.ttpl │ └── services.yaml.ttpl ├── docs │ └── logo.png ├── tools │ ├── phpunit │ │ ├── composer.json │ │ └── phpunit.xml.dist.ttpl │ └── ecs │ │ ├── composer.json │ │ ├── fix │ │ ├── default.bat │ │ └── tests.bat │ │ └── config │ │ └── default.php.ttpl ├── README.md.ttpl ├── .gitignore.txt ├── .gitattributes.txt ├── src │ ├── Model │ │ └── Model.php.ttpl │ ├── Class.php.ttpl │ ├── Session │ │ ├── SessionFactory.php.ttpl │ │ └── Attribute │ │ │ └── ArrayAttributeBag.php.ttpl │ ├── DependencyInjection │ │ ├── Compiler │ │ │ └── AddSessionBagsPass.php.ttpl │ │ ├── Configuration.php.ttpl │ │ └── Extension.php.ttpl │ ├── Controller │ │ ├── ContentElement │ │ │ └── ContentElementController.php.ttpl │ │ ├── Controller.php.ttpl │ │ └── FrontendModule │ │ │ └── FrontendModuleController.php.ttpl │ ├── DataContainer │ │ └── DcaClass.php.ttpl │ └── ContaoManager │ │ └── Plugin.php.ttpl ├── templates │ └── MyCustom │ │ └── my_custom.html.twig.ttpl ├── partials │ └── phpdoc.twig.ttpl ├── .editorconfig.txt ├── composer.json ├── tests │ └── ContaoManager │ │ └── PluginTest.php.ttpl └── .github │ └── workflows │ └── ci.yml ├── docs ├── logo.png ├── backend.png ├── custom-templates.png └── directory-structure.png ├── tools ├── phpstan │ ├── batch │ │ └── phpstan.bat │ └── composer.json ├── phpunit │ ├── composer.json │ └── phpunit.xml.dist └── ecs │ ├── fix │ ├── default.bat │ └── tests.bat │ ├── composer.json │ └── config │ └── default.php ├── config ├── services.yaml └── licenses.yaml ├── contao ├── languages │ ├── en │ │ ├── default.xlf │ │ ├── modules.xlf │ │ └── tl_contao_bundle_creator.xlf │ └── de │ │ ├── default.xlf │ │ └── modules.xlf └── config │ └── config.php ├── .editorconfig ├── src ├── Event │ ├── AddMakerEvent.php │ ├── AddTagsEvent.php │ └── AbstractEvent.php ├── Model │ └── ContaoBundleCreatorModel.php ├── MarkocupicContaoBundleCreatorBundle.php ├── EventSubscriber │ ├── MakerInterface.php │ └── Maker │ │ ├── ContaoManagerPluginClassMaker.php │ │ ├── EasyCodingStandardMaker.php │ │ ├── BundleClassMaker.php │ │ ├── AbstractMaker.php │ │ ├── AddStandardTagsMaker.php │ │ ├── DependencyInjectionExtensionClassMaker.php │ │ ├── ContinuousIntegrationMaker.php │ │ ├── CustomRouteMaker.php │ │ ├── FriendlyConfigurationMaker.php │ │ ├── SessionAttributeBagMaker.php │ │ ├── ComposerJsonMaker.php │ │ ├── AlterRootComposerJsonMaker.php │ │ ├── MiscFilesMaker.php │ │ ├── ContaoContentElementMaker.php │ │ ├── ContaoBackendModuleMaker.php │ │ └── ContaoFrontendModuleMaker.php ├── ContaoManager │ └── Plugin.php ├── DependencyInjection │ ├── MarkocupicContaoBundleCreatorExtension.php │ └── Configuration.php ├── Skeleton.php ├── BundleMaker │ ├── Storage │ │ ├── TagStorage.php │ │ └── FileStorage.php │ ├── Message │ │ └── Message.php │ ├── BundleMaker.php │ └── Str │ │ └── Str.php ├── EventListener │ └── ContaoHook │ │ └── AddCustomRegexpListener.php └── DataContainer │ └── ContaoBundleCreator.php ├── LICENSE ├── composer.json └── README.md /skeleton/public/css/styles.min.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skeleton/contao/templates/twig/.twig-root: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skeleton/public/css/styles.css: -------------------------------------------------------------------------------- 1 | /** Still empty stylesheet **/ -------------------------------------------------------------------------------- /skeleton/public/js/script.js: -------------------------------------------------------------------------------- 1 | /** Still empty script file **/ -------------------------------------------------------------------------------- /skeleton/config/listener.yaml.ttpl: -------------------------------------------------------------------------------- 1 | #config/listener.yaml 2 | services: 3 | 4 | -------------------------------------------------------------------------------- /skeleton/config/parameters.yaml.ttpl: -------------------------------------------------------------------------------- 1 | #config/parameters.yaml 2 | parameters: 3 | 4 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markocupic/contao-bundle-creator-bundle/HEAD/docs/logo.png -------------------------------------------------------------------------------- /docs/backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markocupic/contao-bundle-creator-bundle/HEAD/docs/backend.png -------------------------------------------------------------------------------- /skeleton/docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markocupic/contao-bundle-creator-bundle/HEAD/skeleton/docs/logo.png -------------------------------------------------------------------------------- /docs/custom-templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markocupic/contao-bundle-creator-bundle/HEAD/docs/custom-templates.png -------------------------------------------------------------------------------- /docs/directory-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markocupic/contao-bundle-creator-bundle/HEAD/docs/directory-structure.png -------------------------------------------------------------------------------- /skeleton/tools/phpunit/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "phpunit/phpunit": "^9.5", 4 | "contao/test-case": "^5.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /skeleton/README.md.ttpl: -------------------------------------------------------------------------------- 1 | ![Alt text](docs/logo.png?raw=true "logo") 2 | 3 | # Welcome to {{ bundlename }} 4 | 5 | This bundle is still under construction. 6 | -------------------------------------------------------------------------------- /skeleton/.gitignore.txt: -------------------------------------------------------------------------------- 1 | # Jetbrains 2 | /.idea/* 3 | 4 | # tools 5 | /vendor/ 6 | /tools/*/vendor 7 | /composer.lock 8 | /.php-cs-fixer.cache 9 | /tools/phpunit/.phpunit.result.cache 10 | -------------------------------------------------------------------------------- /skeleton/.gitattributes.txt: -------------------------------------------------------------------------------- 1 | /.ecs 2 | /.github export-ignore 3 | /tests export-ignore 4 | /.gitattributes export-ignore 5 | /.gitignore export-ignore 6 | /phpunit.xml.dist export-ignore 7 | /.travis.yml export-ignore 8 | -------------------------------------------------------------------------------- /skeleton/tools/ecs/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "contao/easy-coding-standard": "^6.13" 4 | }, 5 | "config": { 6 | "allow-plugins": { 7 | "dealerdirect/phpcodesniffer-composer-installer": true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /skeleton/contao/templates/twig/content_element/sample_element.html.twig.ttpl: -------------------------------------------------------------------------------- 1 | {%- verbatim -%} 2 | {% extends "@Contao/content_element/_base.html.twig" %} 3 | {% use "@Contao/component/_download.html.twig" %} 4 | 5 | {% block content %} 6 | {{ text|raw }} 7 | {% endblock %} 8 | {%- endverbatim %} 9 | -------------------------------------------------------------------------------- /skeleton/contao/languages/en/tl_module.php.ttpl: -------------------------------------------------------------------------------- 1 | {{ helloTitle }} 7 |

{{ helloText }} 8 | {% endblock %} 9 | {%- endverbatim %} 10 | -------------------------------------------------------------------------------- /tools/phpstan/batch/phpstan.bat: -------------------------------------------------------------------------------- 1 | :: Run phpstan inside your IDE e.g. PhpStorm (Windows only) 2 | :: Install inside PhpStorm the "Batch Script Support" plugin 3 | cd.. 4 | cd.. 5 | cd.. 6 | cd.. 7 | cd.. 8 | cd.. 9 | php -d memory_limit=-1 vendor/bin/phpstan analyse vendor/markocupic/contao-bundle-creator-bundle/src vendor/markocupic/contao-bundle-creator-bundle/tests 10 | -------------------------------------------------------------------------------- /skeleton/templates/MyCustom/my_custom.html.twig.ttpl: -------------------------------------------------------------------------------- 1 | {% verbatim %} 2 | {% block body %} 3 | 4 |

Welcome to our homepage

5 | 6 |

We are purchasing

7 | 8 | {% for animal in animals %} 9 | 10 | 13 | 14 | {% endfor %} 15 | 16 | {% endblock %} 17 | {% endverbatim %} 18 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: false 6 | bind: 7 | $projectDir: '%kernel.project_dir%' 8 | $zip: '@markocupic.zip_bundle.zip.zip' 9 | 10 | Markocupic\ContaoBundleCreatorBundle\: 11 | resource: ../src/ 12 | exclude: ../src/{DependencyInjection,Model,Event} 13 | -------------------------------------------------------------------------------- /skeleton/partials/phpdoc.twig.ttpl: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of {{ bundlename }}. 3 | * 4 | * (c) {{ composerauthorname }} <{{ composerauthoremail }}> 5 | * @license {{ composerlicense }} 6 | * For the full copyright and license information, 7 | * please view the LICENSE file that was distributed with this source code. 8 | * @link https://github.com/{{ vendorname }}/{{ repositoryname }} 9 | */ 10 | -------------------------------------------------------------------------------- /skeleton/src/Class.php.ttpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Invalid insert! Use "%s" instead of "%s", please. 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tools/phpunit/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "contao/core-bundle": "^4.13 || ^5.0", 4 | "phpunit/phpunit": "^9.5", 5 | "contao/test-case": "^5.0", 6 | "markocupic/zip-bundle": "^1.1", 7 | "ext-json": "*" 8 | }, 9 | "config": { 10 | "allow-plugins": { 11 | "contao-components/installer": true, 12 | "php-http/discovery": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tools/ecs/fix/default.bat: -------------------------------------------------------------------------------- 1 | :: Run easy-coding-standard (ecs) via this batch file inside your IDE e.g. PhpStorm (Windows only) 2 | :: Install inside PhpStorm the "Batch Script Support" plugin 3 | 4 | @echo off 5 | :: Change to the location where the batch file itself is running: 6 | cd /d "%~dp0" 7 | 8 | :: Change to the bundle directory 9 | cd.. 10 | cd.. 11 | cd.. 12 | 13 | ../../../vendor\bin\ecs check src --fix --config tools/ecs/config/default.php 14 | -------------------------------------------------------------------------------- /tools/ecs/fix/tests.bat: -------------------------------------------------------------------------------- 1 | :: Run easy-coding-standard (ecs) via this batch file inside your IDE e.g. PhpStorm (Windows only) 2 | :: Install inside PhpStorm the "Batch Script Support" plugin 3 | 4 | @echo off 5 | :: Change to the location where the batch file itself is running: 6 | cd /d "%~dp0" 7 | 8 | :: Change to the bundle directory 9 | cd.. 10 | cd.. 11 | cd.. 12 | 13 | ../../../vendor\bin\ecs check tests --fix --config tools/ecs/config/default.php 14 | -------------------------------------------------------------------------------- /skeleton/tools/ecs/fix/default.bat: -------------------------------------------------------------------------------- 1 | :: Run easy-coding-standard (ecs) via this batch file inside your IDE e.g. PhpStorm (Windows only) 2 | :: Install inside PhpStorm the "Batch Script Support" plugin 3 | 4 | @echo off 5 | :: Change to the location where the batch file itself is running: 6 | cd /d "%~dp0" 7 | 8 | :: Change to the bundle directory 9 | cd.. 10 | cd.. 11 | cd.. 12 | 13 | ../../../vendor\bin\ecs check src --fix --config tools/ecs/config/default.php 14 | -------------------------------------------------------------------------------- /skeleton/tools/ecs/fix/tests.bat: -------------------------------------------------------------------------------- 1 | :: Run easy-coding-standard (ecs) via this batch file inside your IDE e.g. PhpStorm (Windows only) 2 | :: Install inside PhpStorm the "Batch Script Support" plugin 3 | 4 | @echo off 5 | :: Change to the location where the batch file itself is running: 6 | cd /d "%~dp0" 7 | 8 | :: Change to the bundle directory 9 | cd.. 10 | cd.. 11 | cd.. 12 | 13 | ../../../vendor\bin\ecs check tests --fix --config tools/ecs/config/default.php 14 | -------------------------------------------------------------------------------- /skeleton/contao/dca/tl_module.php.ttpl: -------------------------------------------------------------------------------- 1 | array('{{ dcatable }}') 13 | ); 14 | 15 | /** 16 | * Models 17 | */ 18 | $GLOBALS['TL_MODELS']['{{ dcatable }}'] = {{ modelclassname }}::class; 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /skeleton/contao/dca/tl_content.php.ttpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Invalid insert! Use "%s" instead of "%s", please. 7 | Ungültige Eingabe! Bitte verwenden Sie "%s" anstatt "%s". 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # EditorConfig is awesome: http://EditorConfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Unix-style newlines with a newline ending every file 8 | [*] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.{php,twig,yml,yaml}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.{html5,svg,min.css,min.js}] 18 | insert_final_newline = false 19 | 20 | [*/src/Resources/contao/**.{css,js,php}] 21 | indent_style = tab 22 | 23 | [*/src/Resources/contao/**.html5] 24 | indent_style = space 25 | indent_size = 2 -------------------------------------------------------------------------------- /skeleton/.editorconfig.txt: -------------------------------------------------------------------------------- 1 | 2 | # EditorConfig is awesome: http://EditorConfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Unix-style newlines with a newline ending every file 8 | [*] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.{php,twig,yml,yaml}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.{html5,svg,min.css,min.js}] 18 | insert_final_newline = false 19 | 20 | [*/contao/**.{css,js,php},*/public/**.{css,js}] 21 | indent_style = tab 22 | 23 | [*/contao/**.html5] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /tools/ecs/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "contao/core-bundle": "^4.13 || ^5.0", 4 | "markocupic/zip-bundle": "^1.1", 5 | "contao/easy-coding-standard": "^6.13", 6 | "contao/manager-plugin": "^2.12" 7 | }, 8 | "config": { 9 | "allow-plugins": { 10 | "contao-components/installer": false, 11 | "contao/manager-plugin": false, 12 | "contao-community-alliance/composer-plugin": true, 13 | "php-http/discovery": true, 14 | "dealerdirect/phpcodesniffer-composer-installer": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Event/AddMakerEvent.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\Event; 16 | 17 | class AddMakerEvent extends AbstractEvent 18 | { 19 | public const NAME = 'markocupic.contao_bundle_creator_bundle.maker.added'; 20 | } 21 | -------------------------------------------------------------------------------- /src/Event/AddTagsEvent.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\Event; 16 | 17 | class AddTagsEvent extends AbstractEvent 18 | { 19 | public const NAME = 'markocupic.contao_bundle_creator_bundle.tags.added'; 20 | } 21 | -------------------------------------------------------------------------------- /contao/languages/en/modules.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Developer tools. 7 | 8 | 9 | Contao bundle creator 10 | 11 | 12 | Create a contao bundle. 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Model/ContaoBundleCreatorModel.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\Model; 16 | 17 | use Contao\Model; 18 | 19 | class ContaoBundleCreatorModel extends Model 20 | { 21 | protected static $strTable = 'tl_contao_bundle_creator'; 22 | } 23 | -------------------------------------------------------------------------------- /tools/phpunit/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ../../tests 16 | 17 | 18 | -------------------------------------------------------------------------------- /skeleton/contao/languages/en/default.php.ttpl: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle; 16 | 17 | use Symfony\Component\HttpKernel\Bundle\Bundle; 18 | 19 | class MarkocupicContaoBundleCreatorBundle extends Bundle 20 | { 21 | public function getPath(): string 22 | { 23 | return \dirname(__DIR__); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contao/config/config.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | use Markocupic\ContaoBundleCreatorBundle\Model\ContaoBundleCreatorModel; 16 | 17 | /* 18 | * Backend modules 19 | */ 20 | $GLOBALS['BE_MOD']['dev_tools']['contao_bundle_creator'] = [ 21 | 'tables' => ['tl_contao_bundle_creator'], 22 | ]; 23 | 24 | /* 25 | * Models 26 | */ 27 | $GLOBALS['TL_MODELS']['tl_contao_bundle_creator'] = ContaoBundleCreatorModel::class; 28 | -------------------------------------------------------------------------------- /contao/languages/de/modules.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Developer tools. 7 | Entwickler Tools 8 | 9 | 10 | Contao bundle creator 11 | Contao Bundle Generator 12 | 13 | 14 | Create a contao bundle. 15 | Erstellen Sie ein Contao Bundle. 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /skeleton/src/Session/SessionFactory.php.ttpl: -------------------------------------------------------------------------------- 1 | inner->createSession(); 24 | $session->registerBag($this->sessionBag); 25 | 26 | return $session; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/EventSubscriber/MakerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 19 | 20 | /** 21 | * Interface that all maker commands must implement. 22 | */ 23 | interface MakerInterface 24 | { 25 | public function addTagsToStorage(AddTagsEvent $event): void; 26 | 27 | public function addFilesToStorage(AddMakerEvent $event): void; 28 | } 29 | -------------------------------------------------------------------------------- /skeleton/src/Session/Attribute/ArrayAttributeBag.php.ttpl: -------------------------------------------------------------------------------- 1 | has($offset); 19 | } 20 | 21 | public function &offsetGet($offset): mixed 22 | { 23 | return $this->attributes[$offset]; 24 | } 25 | 26 | public function offsetSet($offset, $value): void 27 | { 28 | $this->set($offset, $value); 29 | } 30 | 31 | public function offsetUnset($offset): void 32 | { 33 | $this->remove($offset); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /skeleton/src/DependencyInjection/Compiler/AddSessionBagsPass.php.ttpl: -------------------------------------------------------------------------------- 1 | has('session')) { 21 | return; 22 | } 23 | 24 | $session = $container->findDefinition('session'); 25 | $session->addMethodCall('registerBag', [new Reference('{{ toplevelnamespace }}\{{ sublevelnamespace }}\Session\Attribute\ArrayAttributeBag')]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /skeleton/tools/ecs/config/default.php.ttpl: -------------------------------------------------------------------------------- 1 | withSets([SetList::CONTAO]) 12 | ->withPaths([ 13 | __DIR__ . '/../../src', 14 | ]) 15 | ->withSkip([ 16 | \Contao\EasyCodingStandard\Fixer\CommentLengthFixer::class => ['*.php'], 17 | \PhpCsFixer\Fixer\Whitespace\MethodChainingIndentationFixer::class => [ 18 | '*/DependencyInjection/Configuration.php', 19 | ], 20 | ]) 21 | ->withRootFiles() 22 | ->withParallel() 23 | ->withSpacing(Option::INDENTATION_SPACES, "\n") 24 | ->withConfiguredRule(HeaderCommentFixer::class, [ 25 | 'header' => "{{ ecsphpdoc }}", 26 | ]) 27 | ->withCache(sys_get_temp_dir() . '/ecs/{{ vendorname }}/{{ repositoryname }}'); 28 | -------------------------------------------------------------------------------- /skeleton/contao/languages/en/modules.php.ttpl: -------------------------------------------------------------------------------- 1 | set('text', $model->text); 24 | 25 | return $template->getResponse(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /skeleton/tools/phpunit/phpunit.xml.dist.ttpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | 20 | 21 | ./src 22 | 23 | ./contao 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/ContaoManager/Plugin.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\ContaoManager; 16 | 17 | use Contao\CoreBundle\ContaoCoreBundle; 18 | use Contao\ManagerPlugin\Bundle\BundlePluginInterface; 19 | use Contao\ManagerPlugin\Bundle\Config\BundleConfig; 20 | use Contao\ManagerPlugin\Bundle\Parser\ParserInterface; 21 | use Markocupic\ContaoBundleCreatorBundle\MarkocupicContaoBundleCreatorBundle; 22 | 23 | class Plugin implements BundlePluginInterface 24 | { 25 | public function getBundles(ParserInterface $parser): array 26 | { 27 | return [ 28 | BundleConfig::create(MarkocupicContaoBundleCreatorBundle::class) 29 | ->setLoadAfter([ContaoCoreBundle::class]), 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marko Cupic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /skeleton/src/DependencyInjection/Configuration.php.ttpl: -------------------------------------------------------------------------------- 1 | getRootNode() 21 | ->children() 22 | ->arrayNode('foo') 23 | ->addDefaultsIfNotSet() 24 | ->children() 25 | ->scalarNode('bar') 26 | ->cannotBeEmpty() 27 | ->defaultValue('***') 28 | ->end() 29 | ->end() 30 | ->end() // end foo 31 | ->end() 32 | ; 33 | 34 | return $treeBuilder; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /skeleton/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "description": "", 4 | "keywords": [ 5 | "contao", 6 | "bundle", 7 | "tag 3", 8 | "tag 4" 9 | ], 10 | "type": "contao-bundle", 11 | "license": "", 12 | "authors": [], 13 | "support": { 14 | "issues": "", 15 | "source": "" 16 | }, 17 | "require": { 18 | "php": "^8.1", 19 | "contao/core-bundle": "^5.3" 20 | }, 21 | "require-dev": { 22 | "contao/manager-plugin": "^2.12", 23 | "contao/easy-coding-standard": "^6.13" 24 | }, 25 | "autoload": { 26 | "psr-4": {} 27 | }, 28 | "config": { 29 | "allow-plugins": { 30 | "contao-components/installer": false, 31 | "contao/manager-plugin": false, 32 | "contao-community-alliance/composer-plugin": true 33 | } 34 | }, 35 | "extra": {}, 36 | "scripts": { 37 | "cs-fixer": "@php tools/ecs/vendor/bin/ecs check config/ contao/ src/ templates/ tests/ --config tools/ecs/config/default.php --fix --ansi", 38 | "unit-tests": "@php tools/phpunit/vendor/bin/phpunit -c tools/phpunit/phpunit.xml.dist" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/DependencyInjection/MarkocupicContaoBundleCreatorExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\DependencyInjection; 16 | 17 | use Symfony\Component\Config\FileLocator; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | use Symfony\Component\DependencyInjection\Extension\Extension; 20 | use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; 21 | 22 | class MarkocupicContaoBundleCreatorExtension extends Extension 23 | { 24 | /** 25 | * @throws \Exception 26 | */ 27 | public function load(array $configs, ContainerBuilder $container): void 28 | { 29 | $loader = new YamlFileLoader( 30 | $container, 31 | new FileLocator(__DIR__.'/../../config'), 32 | ); 33 | 34 | $loader->load('services.yaml'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /skeleton/src/DataContainer/DcaClass.php.ttpl: -------------------------------------------------------------------------------- 1 | framework->getAdapter(Input::class); 26 | $systemAdapter = $this->framework->getAdapter(System::class); 27 | 28 | $systemAdapter->loadLanguageFile('{{ dcatable }}'); 29 | 30 | if ('edit' === $inputAdapter->get('act')) { 31 | $arrButtons['customButton'] = ''; 32 | } 33 | 34 | return $arrButtons; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tools/ecs/config/default.php: -------------------------------------------------------------------------------- 1 | withSets([SetList::CONTAO]) 12 | ->withPaths([ 13 | __DIR__ . '/../../src', 14 | ]) 15 | ->withSkip([ 16 | \Contao\EasyCodingStandard\Fixer\CommentLengthFixer::class => ['*.php'], 17 | \PhpCsFixer\Fixer\Whitespace\MethodChainingIndentationFixer::class => [ 18 | '*/DependencyInjection/Configuration.php', 19 | ], 20 | ]) 21 | ->withRootFiles() 22 | ->withParallel() 23 | ->withSpacing(Option::INDENTATION_SPACES, "\n") 24 | ->withConfiguredRule(HeaderCommentFixer::class, [ 25 | 'header' => "This file is part of Contao Bundle Creator Bundle.\n\n(c) Marko Cupic \n@license MIT\nFor the full copyright and license information,\nplease view the LICENSE file that was distributed with this source code.\n@link https://github.com/markocupic/contao-bundle-creator-bundle", 26 | ]) 27 | ->withCache(sys_get_temp_dir() . '/ecs/markocupic/contao-bundle-creator-bundle'); 28 | -------------------------------------------------------------------------------- /src/Skeleton.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle; 16 | 17 | use Symfony\Component\Filesystem\Path; 18 | 19 | class Skeleton 20 | { 21 | private static string|null $customSkeletonPath; 22 | 23 | private static string|null $defaultSkeletonPath; 24 | 25 | public static function getDefaultPath(): string 26 | { 27 | return static::$defaultSkeletonPath ?? Path::normalize(Path::join(__DIR__, '/../skeleton')); 28 | } 29 | 30 | public static function getCustomTemplatePath(): string 31 | { 32 | return static::$customSkeletonPath ?? Path::normalize(Path::join(__DIR__, '/../../../../templates/contao-bundle-creator-bundle/skeleton')); 33 | } 34 | 35 | public static function setCustomTemplatePath(string $customTemplatePath): void 36 | { 37 | static::$customSkeletonPath = $customTemplatePath; 38 | } 39 | 40 | public static function setDefaultPath(string $defaultSkeletonPath): void 41 | { 42 | static::$defaultSkeletonPath = $defaultSkeletonPath; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /skeleton/contao/languages/en/tl_sample_table.php.ttpl: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Plugin::class, new Plugin()); 24 | } 25 | 26 | /** 27 | * Test returns the bundles. 28 | */ 29 | public function testGetBundles(): void 30 | { 31 | $plugin = new Plugin(); 32 | 33 | /** @var array $bundles */ 34 | $bundles = $plugin->getBundles(new DelegatingParser()); 35 | 36 | $this->assertCount(1, $bundles); 37 | $this->assertInstanceOf(BundleConfig::class, $bundles[0]); 38 | $this->assertSame({{ toplevelnamespace }}{{ sublevelnamespace }}::class, $bundles[0]->getName()); 39 | $this->assertSame([ContaoCoreBundle::class], $bundles[0]->getLoadAfter()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /skeleton/src/Controller/Controller.php.ttpl: -------------------------------------------------------------------------------- 1 | 'frontend', '_token_check' => true])] 15 | class MyCustomController extends AbstractController 16 | { 17 | public function __construct( 18 | private readonly Twig $twig, 19 | ) { 20 | } 21 | 22 | public function __invoke(): Response 23 | { 24 | $animals = [ 25 | [ 26 | 'species' => 'dogs', 27 | 'color' => 'white', 28 | ], 29 | [ 30 | 'species' => 'birds', 31 | 'color' => 'black', 32 | ], [ 33 | 'species' => 'cats', 34 | 'color' => 'pink', 35 | ], [ 36 | 'species' => 'cows', 37 | 'color' => 'yellow', 38 | ], 39 | ]; 40 | 41 | return new Response($this->twig->render( 42 | '{{ twignamespace }}/MyCustom/my_custom.html.twig', 43 | [ 44 | 'animals' => $animals, 45 | ], 46 | )); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /skeleton/src/DependencyInjection/Extension.php.ttpl: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 32 | {% endif %} 33 | 34 | $loader = new YamlFileLoader( 35 | $container, 36 | new FileLocator(__DIR__.'/../../config'), 37 | ); 38 | 39 | $loader->load('parameters.yaml'); 40 | $loader->load('services.yaml'); 41 | $loader->load('listener.yaml'); 42 | {% if addFriendlyConfiguration %} 43 | $rootKey = $this->getAlias(); 44 | 45 | $container->setParameter($rootKey.'.foo.bar', $config['foo']['bar']); 46 | {% endif %} 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /skeleton/src/ContaoManager/Plugin.php.ttpl: -------------------------------------------------------------------------------- 1 | setLoadAfter([ContaoCoreBundle::class]), 28 | ]; 29 | } 30 | {% if addCustomRoute %} 31 | 32 | /** 33 | * @return RouteCollection|null 34 | */ 35 | public function getRouteCollection(LoaderResolverInterface $resolver, KernelInterface $kernel) 36 | { 37 | return $resolver 38 | ->resolve(__DIR__.'/../Controller') 39 | ->load(__DIR__.'/../Controller') 40 | ; 41 | } 42 | {% endif %} 43 | } 44 | -------------------------------------------------------------------------------- /src/BundleMaker/Storage/TagStorage.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage; 16 | 17 | class TagStorage 18 | { 19 | private array $tags = []; 20 | 21 | public function set(string $key, string $value): void 22 | { 23 | $this->tags[$key] = $value; 24 | } 25 | 26 | /** 27 | * @throws \Exception 28 | */ 29 | public function get(string $key): string 30 | { 31 | if (!\array_key_exists($key, $this->tags)) { 32 | throw new \Exception(\sprintf('Tag "%s" not found.', $key)); 33 | } 34 | 35 | return $this->tags[$key]; 36 | } 37 | 38 | public function getAll(): array 39 | { 40 | return $this->tags; 41 | } 42 | 43 | public function has(string $key): bool 44 | { 45 | if (\array_key_exists($key, $this->tags)) { 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | public function remove(string $key): void 53 | { 54 | if (\array_key_exists($key, $this->tags)) { 55 | unset($this->tags[$key]); 56 | } 57 | } 58 | 59 | /** 60 | * Remove all tags. 61 | */ 62 | public function removeAll(): void 63 | { 64 | $this->tags = []; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /config/licenses.yaml: -------------------------------------------------------------------------------- 1 | licenses: 2 | AFL-3.0: Academic Free License v3.0 3 | APACHE-2.0: Apache license 2.0 4 | ARTISTIC-2.0: Artistic license 2.0 5 | BSL-1.0: Boost Software License 1.0 6 | BSD-2-CLAUSE: BSD 2-clause "Simplified" license 7 | BSD-3-CLAUSE: BSD 3-clause "New" or "Revised" license 8 | BSD-3-CLAUSE-CLEAR: BSD 3-clause Clear license 9 | CC: Creative Commons license family 10 | CC0-1.0: Creative Commons Zero v1.0 Universal 11 | CC-BY-4.0: Creative Commons Attribution 4.0 12 | CC-BY-SA-4.0: Creative Commons Attribution Share Alike 4.0 13 | WTFPL: Do What The F*ck You Want To Public License 14 | ECL-2.0: Educational Community License v2.0 15 | EPL-1.0: Eclipse Public License 1.0 16 | EUPL-1.1: European Union Public License 1.1 17 | AGPL-3.0: GNU Affero General Public License v3.0 18 | GPL: GNU General Public License family 19 | GPL-2.0: GNU General Public License v2.0 20 | GPL-3.0: GNU General Public License v3.0 21 | GPL-3.0-or-later: GNU General Public License v3.0 or later 22 | LGPL: GNU Lesser General Public License family 23 | LGPL-2.1: GNU Lesser General Public License v2.1 24 | LGPL-3.0: GNU Lesser General Public License v3.0 25 | LGPL-3.0-or-later: GNU Lesser General Public License v3.0 or later 26 | ISC: ISC 27 | LPPL-1.3C: LaTeX Project Public License v1.3c 28 | MS-PL: Microsoft Public License 29 | MIT: MIT 30 | MPL-2.0: Mozilla Public License 2.0 31 | OSL-3.0: Open Software License 3.0 32 | POSTGRESQL: PostgreSQL License 33 | OFL-1.1: SIL Open Font License 1.1 34 | NCSA: University of Illinois/NCSA Open Source License 35 | UNLICENSE: The Unlicense 36 | ZLIB: zLib License 37 | -------------------------------------------------------------------------------- /skeleton/config/services.yaml.ttpl: -------------------------------------------------------------------------------- 1 | # config/services.yaml 2 | services: 3 | _defaults: 4 | autowire: true # Automatically injects dependencies in your services. 5 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 6 | public: false # Allows optimizing the container by removing unused services; this also means 7 | # fetching services directly from the container via $container->get() won't work. 8 | # The best practice is to be explicit about your dependencies anyway. 9 | bind: 10 | #$projectDir: '%kernel.project_dir%' 11 | 12 | {{ toplevelnamespace }}\{{ sublevelnamespace }}\: 13 | resource: ../src/ 14 | exclude: ../src/{DependencyInjection,Model,Session} 15 | 16 | {% if addSessionAttribute|default %} 17 | # Add a session bag 18 | {{ servicevendornamekey }}.{{ servicerepositorynamekey }}.session.factory: 19 | class: {{ toplevelnamespace }}\{{ sublevelnamespace }}\Session\SessionFactory 20 | decorates: session.factory 21 | arguments: 22 | - '@{{ servicevendornamekey }}.{{ servicerepositorynamekey }}.session.factory.inner' 23 | - '@{{ servicevendornamekey }}.{{ servicerepositorynamekey }}.session.attribute.array_attribute_bag' 24 | 25 | {{ servicevendornamekey }}.{{ servicerepositorynamekey }}.session.attribute.array_attribute_bag: 26 | class: {{ toplevelnamespace }}\{{ sublevelnamespace }}\Session\Attribute\ArrayAttributeBag 27 | arguments: 28 | - {{ sessionAttributeKey }} 29 | calls: 30 | - [ setName, [ {{ sessionAttributeName }} ] ] 31 | {% endif %} 32 | -------------------------------------------------------------------------------- /tools/phpstan/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "chillerlan/php-qrcode": "^5.0", 4 | "code4nix/uri-signer": "^1.0", 5 | "codefog/contao-haste": "^5.0", 6 | "contao/calendar-bundle": "^5.3", 7 | "contao/comments-bundle": "^5.3", 8 | "contao/core-bundle": "^5.3", 9 | "contao/newsletter-bundle": "^5.3", 10 | "contao/test-case": "^5.0", 11 | "eluceo/ical": "^2.5", 12 | "league/csv": "^9.7", 13 | "markocupic/cloudconvert-bundle": "^2.0", 14 | "markocupic/composer-file-copier-plugin": "^0.2", 15 | "markocupic/contao-component-fontawesome-free": "^6.0", 16 | "markocupic/contao-component-vue-js": "^3.3", 17 | "markocupic/contao-custom-global-operation": "^1.0", 18 | "markocupic/contao-frontend-user-notification": "^1.0", 19 | "markocupic/contao-twig-assets": "^1.0", 20 | "markocupic/contao-utf8-arrows-insert-tag-bundle": "^1.0", 21 | "markocupic/phpoffice-bundle": "^1.3", 22 | "markocupic/rss-feed-generator-bundle": "^1.1", 23 | "markocupic/swiss-alpine-club-contao-login-client-bundle": "^3.0", 24 | "markocupic/zip-bundle": "^1.0", 25 | "menatwork/contao-multicolumnwizard-bundle": "^3.5", 26 | "phpstan/phpstan": "^1.10", 27 | "phpunit/phpunit": "^9.5", 28 | "ramsey/uuid": "^3.8 || ^4.3", 29 | "symfony/stopwatch": "*", 30 | "tecnickcom/tcpdf": "^6.4", 31 | "terminal42/notification_center": "^2.0" 32 | }, 33 | "config": { 34 | "allow-plugins": { 35 | "contao-components/installer": false, 36 | "contao/manager-plugin": false, 37 | "contao-community-alliance/composer-plugin": true, 38 | "markocupic/composer-file-copier-plugin": true, 39 | "php-http/discovery": true, 40 | "dealerdirect/phpcodesniffer-composer-installer": true 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/ContaoManagerPluginClassMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 19 | 20 | final class ContaoManagerPluginClassMaker extends AbstractMaker 21 | { 22 | public const PRIORITY = 970; 23 | 24 | public static function getSubscribedEvents(): array 25 | { 26 | return [ 27 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 28 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 29 | ]; 30 | } 31 | 32 | public function addTagsToStorage(AddTagsEvent $event): void 33 | { 34 | parent::addTagsToStorage($event); 35 | } 36 | 37 | /** 38 | * Add the Contao Manager Plugin class to file storage. 39 | * 40 | * @throws \Exception 41 | */ 42 | public function addFilesToStorage(AddMakerEvent $event): void 43 | { 44 | parent::addFilesToStorage($event); 45 | 46 | $source = \sprintf( 47 | '%s/src/ContaoManager/Plugin.php.ttpl', 48 | $this->skeletonPath, 49 | ); 50 | 51 | $target = \sprintf( 52 | '%s/vendor/%s/%s/src/ContaoManager/Plugin.php', 53 | $this->projectDir, 54 | $this->input->vendorname, 55 | $this->input->repositoryname, 56 | ); 57 | 58 | if (!$this->fileStorage->has($target)) { 59 | $this->fileStorage->addFile($source, $target); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/EasyCodingStandardMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 19 | 20 | final class EasyCodingStandardMaker extends AbstractMaker 21 | { 22 | public const PRIORITY = 940; 23 | 24 | public static function getSubscribedEvents(): array 25 | { 26 | return [ 27 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 28 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 29 | ]; 30 | } 31 | 32 | public function addTagsToStorage(AddTagsEvent $event): void 33 | { 34 | parent::addTagsToStorage($event); 35 | } 36 | 37 | /** 38 | * Add easy coding standard config files to the bundle. 39 | * 40 | * @throws \Exception 41 | */ 42 | public function addFilesToStorage(AddMakerEvent $event): void 43 | { 44 | parent::addFilesToStorage($event); 45 | 46 | if (!$this->input->addEasyCodingStandard) { 47 | return; 48 | } 49 | 50 | // tools/ecs/*.* 51 | $source = \sprintf( 52 | '%s/tools/ecs', 53 | $this->skeletonPath, 54 | ); 55 | 56 | $target = \sprintf( 57 | '%s/vendor/%s/%s/tools/ecs', 58 | $this->projectDir, 59 | $this->input->vendorname, 60 | $this->input->repositoryname, 61 | ); 62 | 63 | // Add to storage 64 | $this->fileStorage->addFilesFromFolder($source, $target, true); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/BundleClassMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 20 | 21 | final class BundleClassMaker extends AbstractMaker 22 | { 23 | public const PRIORITY = 990; 24 | 25 | public static function getSubscribedEvents(): array 26 | { 27 | return [ 28 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 29 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 30 | ]; 31 | } 32 | 33 | public function addTagsToStorage(AddTagsEvent $event): void 34 | { 35 | parent::addTagsToStorage($event); 36 | } 37 | 38 | /** 39 | * Add the bundle class to file storage. 40 | * 41 | * @throws \Exception 42 | */ 43 | public function addFilesToStorage(AddMakerEvent $event): void 44 | { 45 | parent::addFilesToStorage($event); 46 | 47 | /** @var Str $strAdapter */ 48 | $strAdapter = $this->framework->getAdapter(Str::class); 49 | 50 | $source = \sprintf( 51 | '%s/src/Class.php.ttpl', 52 | $this->skeletonPath, 53 | ); 54 | 55 | $target = \sprintf( 56 | '%s/vendor/%s/%s/src/%s%s.php', 57 | $this->projectDir, 58 | $this->input->vendorname, 59 | $this->input->repositoryname, 60 | $strAdapter->asClassName((string) $this->input->vendorname), 61 | $strAdapter->asClassName((string) $this->input->repositoryname), 62 | ); 63 | 64 | if (!$this->fileStorage->has($target)) { 65 | $this->fileStorage->addFile($source, $target); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markocupic/contao-bundle-creator-bundle", 3 | "description": "This bundle provides a bundle maker for Contao 4.*. The extension will create a fully working backend- or/and frontend module after you have defined a few parameters in the contao backend.", 4 | "keywords": [ 5 | "contao", 6 | "bundle", 7 | "maker", 8 | "creator", 9 | "extension", 10 | "boilerplate", 11 | "skeleton" 12 | ], 13 | "type": "contao-bundle", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Marko Cupic", 18 | "email": "m.cupic@gmx.ch", 19 | "homepage": "https://github.com/markocupic", 20 | "role": "Developer" 21 | } 22 | ], 23 | "support": { 24 | "issues": "https://github.com/markocupic/contao-bundle-creator-bundle/issues", 25 | "source": "https://github.com/markocupic/contao-bundle-creator-bundle" 26 | }, 27 | "require": { 28 | "php": "^8.1", 29 | "contao/core-bundle": "^5.3", 30 | "markocupic/zip-bundle": "^1.1", 31 | "ext-json": "*" 32 | }, 33 | "require-dev": { 34 | "contao/manager-plugin": "^2.12", 35 | "contao/easy-coding-standard": "^6.13", 36 | "phpstan/phpstan": "^2.1" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Markocupic\\ContaoBundleCreatorBundle\\": "src/" 41 | } 42 | }, 43 | "extra": { 44 | "contao-manager-plugin": "Markocupic\\ContaoBundleCreatorBundle\\ContaoManager\\Plugin" 45 | }, 46 | "config": { 47 | "allow-plugins": { 48 | "contao/manager-plugin": false, 49 | "contao-components/installer": false, 50 | "contao-community-alliance/composer-plugin": true, 51 | "dealerdirect/phpcodesniffer-composer-installer": true 52 | } 53 | }, 54 | "scripts": { 55 | "cs-fixer": "@php tools/ecs/vendor/bin/ecs check config/ src/ tests/ --config tools/ecs/config/default.php --fix --ansi", 56 | "phpstan": "@php tools/phpstan/vendor/bin/phpstan analyse src tests", 57 | "unit-tests": "@php tools/phpunit/vendor/bin/phpunit -c tools/phpunit/phpunit.xml.dist" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /skeleton/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: ~ 5 | push: 6 | branches: 7 | - '*' 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | cs: 13 | name: Coding Style 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | 22 | - name: Install ecs 23 | run: cd tools/ecs && composer update --no-interaction --no-suggest 24 | 25 | - name: Run the CS fixer 26 | run: composer cs-fixer 27 | 28 | tests: 29 | name: PHP ${{ matrix.php }} 30 | runs-on: ubuntu-latest 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | php: [ 8.1, 8.2, 8.3, 8.4, 8.5 ] 35 | steps: 36 | - name: Setup PHP 37 | uses: shivammathur/setup-php@v2 38 | with: 39 | php-version: ${{ matrix.php }} 40 | 41 | - name: Checkout 42 | uses: actions/checkout@v2 43 | 44 | - name: Install phpunit 45 | run: cd tools/phpunit && composer update --no-interaction --no-suggest 46 | 47 | - name: Install the dependencies 48 | run: composer update --no-interaction --no-suggest 49 | 50 | - name: Run the unit tests 51 | run: composer unit-tests 52 | 53 | prefer-lowest-tests: 54 | name: PHP ${{ matrix.php }} --prefer-lowest 55 | runs-on: ubuntu-latest 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | php: [ 8.1, 8.2, 8.3, 8.4, 8.5 ] 60 | steps: 61 | - name: Setup PHP 62 | uses: shivammathur/setup-php@v2 63 | with: 64 | php-version: ${{ matrix.php }} 65 | 66 | - name: Checkout 67 | uses: actions/checkout@v2 68 | 69 | - name: Install phpunit 70 | run: cd tools/phpunit && composer update --no-interaction --no-suggest 71 | 72 | - name: Install the dependencies 73 | run: composer update --prefer-lowest --prefer-stable --no-interaction --no-suggest 74 | 75 | - name: Run the unit tests 76 | run: composer unit-tests 77 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/AbstractMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Contao\CoreBundle\Framework\ContaoFramework; 18 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Message\Message; 19 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage\FileStorage; 20 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage\TagStorage; 21 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 22 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 23 | use Markocupic\ContaoBundleCreatorBundle\EventSubscriber\MakerInterface; 24 | use Markocupic\ContaoBundleCreatorBundle\Model\ContaoBundleCreatorModel; 25 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 26 | use Symfony\Component\HttpFoundation\Session\SessionInterface; 27 | 28 | abstract class AbstractMaker implements MakerInterface, EventSubscriberInterface 29 | { 30 | protected ContaoFramework $framework; 31 | 32 | protected SessionInterface $session; 33 | 34 | protected TagStorage $tagStorage; 35 | 36 | protected FileStorage $fileStorage; 37 | 38 | protected ContaoBundleCreatorModel $input; 39 | 40 | protected Message $message; 41 | 42 | protected string $skeletonPath; 43 | 44 | protected string $projectDir; 45 | 46 | public function addFilesToStorage(AddMakerEvent $event): void 47 | { 48 | $this->initialize($event); 49 | } 50 | 51 | public function addTagsToStorage(AddTagsEvent $event): void 52 | { 53 | $this->initialize($event); 54 | } 55 | 56 | /** 57 | * @param AddTagsEvent|AddMakerEvent $event 58 | */ 59 | private function initialize($event): void 60 | { 61 | $this->framework = $event->getFramework(); 62 | $this->session = $event->getSession(); 63 | $this->tagStorage = $event->getTagStorage(); 64 | $this->fileStorage = $event->getFileStorage(); 65 | $this->input = $event->getInput(); 66 | $this->message = $event->getMessage(); 67 | $this->skeletonPath = $event->getSkeletonPath(); 68 | $this->projectDir = $event->getProjectDir(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Event/AbstractEvent.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\Event; 16 | 17 | use Contao\CoreBundle\Framework\ContaoFramework; 18 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Message\Message; 19 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage\FileStorage; 20 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage\TagStorage; 21 | use Markocupic\ContaoBundleCreatorBundle\Model\ContaoBundleCreatorModel; 22 | use Symfony\Component\HttpFoundation\RequestStack; 23 | use Symfony\Component\HttpFoundation\Session\SessionInterface; 24 | use Symfony\Contracts\EventDispatcher\Event; 25 | 26 | abstract class AbstractEvent extends Event 27 | { 28 | private SessionInterface $session; 29 | 30 | public function __construct( 31 | private readonly ContaoFramework $framework, 32 | private readonly RequestStack $requestStack, 33 | private readonly TagStorage $tagStorage, 34 | private readonly FileStorage $fileStorage, 35 | private readonly ContaoBundleCreatorModel $input, 36 | private readonly Message $message, 37 | private readonly string $skeletonPath, 38 | private readonly string $projectDir, 39 | ) { 40 | $this->session = $this->requestStack->getCurrentRequest()->getSession(); 41 | } 42 | 43 | public function getFramework(): ContaoFramework 44 | { 45 | return $this->framework; 46 | } 47 | 48 | public function getSession(): SessionInterface 49 | { 50 | return $this->session; 51 | } 52 | 53 | public function getTagStorage(): TagStorage 54 | { 55 | return $this->tagStorage; 56 | } 57 | 58 | public function getFileStorage(): FileStorage 59 | { 60 | return $this->fileStorage; 61 | } 62 | 63 | public function getInput(): ContaoBundleCreatorModel 64 | { 65 | return $this->input; 66 | } 67 | 68 | public function getMessage(): Message 69 | { 70 | return $this->message; 71 | } 72 | 73 | public function getSkeletonPath(): string 74 | { 75 | return $this->skeletonPath; 76 | } 77 | 78 | public function getProjectDir(): string 79 | { 80 | return $this->projectDir; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/AddStandardTagsMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 20 | 21 | final class AddStandardTagsMaker extends AbstractMaker 22 | { 23 | public const PRIORITY = 10000; 24 | 25 | public static function getSubscribedEvents(): array 26 | { 27 | return [ 28 | // This subscriber should be executed first!!! 29 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 30 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 31 | ]; 32 | } 33 | 34 | /** 35 | * Add some default tags and the phpdoc tag to the tag storage. 36 | * 37 | * @throws \Exception 38 | */ 39 | public function addTagsToStorage(AddTagsEvent $event): void 40 | { 41 | parent::addTagsToStorage($event); 42 | 43 | // Store input values into the tag storage 44 | foreach ($this->input->row() as $fieldname => $value) { 45 | $this->tagStorage->set((string) $fieldname, (string) $value); 46 | } 47 | 48 | /** @var Str $strAdapter */ 49 | $strAdapter = $this->framework->getAdapter(Str::class); 50 | 51 | // Namespaces 52 | $this->tagStorage->set('toplevelnamespace', $strAdapter->asClassName((string) $this->input->vendorname)); 53 | $this->tagStorage->set('sublevelnamespace', $strAdapter->asClassName((string) $this->input->repositoryname)); 54 | 55 | // Current year 56 | $this->tagStorage->set('year', date('Y')); 57 | 58 | // Phpdoc 59 | $strPhpdoc = $this->fileStorage->getTagReplacedContentFromFilePath(\sprintf('%s/partials/phpdoc.twig.ttpl', $this->skeletonPath), $this->tagStorage); 60 | $this->tagStorage->set('phpdoc', rtrim($strPhpdoc, " \t\n\r\0\x0B")); 61 | $this->tagStorage->set('ecsphpdoc', Str::generatePhpDocStringForECS($strPhpdoc)); 62 | } 63 | 64 | public function addFilesToStorage(AddMakerEvent $event): void 65 | { 66 | parent::addFilesToStorage($event); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/DependencyInjectionExtensionClassMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 20 | 21 | final class DependencyInjectionExtensionClassMaker extends AbstractMaker 22 | { 23 | public const PRIORITY = 980; 24 | 25 | public static function getSubscribedEvents(): array 26 | { 27 | return [ 28 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 29 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 30 | ]; 31 | } 32 | 33 | public function addTagsToStorage(AddTagsEvent $event): void 34 | { 35 | parent::addTagsToStorage($event); 36 | 37 | /** @var Str $strAdapter */ 38 | $strAdapter = $this->framework->getAdapter(Str::class); 39 | 40 | $this->tagStorage->set('dependencyinjectionextensionclassname', $strAdapter->asDependencyInjectionExtensionClassName((string) $this->input->vendorname, (string) $this->input->repositoryname)); 41 | } 42 | 43 | /** 44 | * Add the Dependency Injection Extension class to file storage. 45 | * 46 | * @throws \Exception 47 | */ 48 | public function addFilesToStorage(AddMakerEvent $event): void 49 | { 50 | parent::addFilesToStorage($event); 51 | 52 | /** @var Str $strAdapter */ 53 | $strAdapter = $this->framework->getAdapter(Str::class); 54 | 55 | $source = \sprintf( 56 | '%s/src/DependencyInjection/Extension.php.ttpl', 57 | $this->skeletonPath, 58 | ); 59 | 60 | $target = \sprintf( 61 | '%s/vendor/%s/%s/src/DependencyInjection/%s.php', 62 | $this->projectDir, 63 | $this->input->vendorname, 64 | $this->input->repositoryname, 65 | $strAdapter->asDependencyInjectionExtensionClassName((string) $this->input->vendorname, (string) $this->input->repositoryname), 66 | ); 67 | 68 | if (!$this->fileStorage->has($target)) { 69 | $this->fileStorage->addFile($source, $target); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/ContinuousIntegrationMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 19 | 20 | final class ContinuousIntegrationMaker extends AbstractMaker 21 | { 22 | public const PRIORITY = 960; 23 | 24 | public static function getSubscribedEvents(): array 25 | { 26 | return [ 27 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 28 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 29 | ]; 30 | } 31 | 32 | public function addTagsToStorage(AddTagsEvent $event): void 33 | { 34 | parent::addTagsToStorage($event); 35 | } 36 | 37 | /** 38 | * Add unit tests to the file storage. 39 | * 40 | * @throws \Exception 41 | */ 42 | public function addFilesToStorage(AddMakerEvent $event): void 43 | { 44 | parent::addFilesToStorage($event); 45 | 46 | // tools/phpunit/*.* 47 | $source = \sprintf( 48 | '%s/tools/phpunit', 49 | $this->skeletonPath, 50 | ); 51 | 52 | $target = \sprintf( 53 | '%s/vendor/%s/%s/tools/phpunit', 54 | $this->projectDir, 55 | $this->input->vendorname, 56 | $this->input->repositoryname, 57 | ); 58 | 59 | // Add to storage 60 | $this->fileStorage->addFilesFromFolder($source, $target, true); 61 | 62 | // Add plugin test 63 | $source = \sprintf( 64 | '%s/tests/ContaoManager/PluginTest.php.ttpl', 65 | $this->skeletonPath, 66 | ); 67 | 68 | $target = \sprintf( 69 | '%s/vendor/%s/%s/tests/ContaoManager/PluginTest.php', 70 | $this->projectDir, 71 | $this->input->vendorname, 72 | $this->input->repositoryname, 73 | ); 74 | 75 | if (!$this->fileStorage->has($target)) { 76 | $this->fileStorage->addFile($source, $target); 77 | } 78 | 79 | // Add github workflow/ci.yml file 80 | $source = \sprintf( 81 | '%s/.github/workflows/ci.yml', 82 | $this->skeletonPath, 83 | ); 84 | 85 | $target = \sprintf( 86 | '%s/vendor/%s/%s/.github/workflows/ci.yml', 87 | $this->projectDir, 88 | $this->input->vendorname, 89 | $this->input->repositoryname, 90 | ); 91 | 92 | if (!$this->fileStorage->has($target)) { 93 | $this->fileStorage->addFile($source, $target); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/EventListener/ContaoHook/AddCustomRegexpListener.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventListener\ContaoHook; 16 | 17 | use Contao\CoreBundle\DependencyInjection\Attribute\AsHook; 18 | use Contao\Widget; 19 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 20 | 21 | class AddCustomRegexpListener 22 | { 23 | /** 24 | * @throws \Exception 25 | */ 26 | #[AsHook('addCustomRegexp', priority: 100)] 27 | public function cbcbRegexp(string $regexp, $input, Widget $widget): bool 28 | { 29 | if (preg_match('/^cbcb_(.*)$/', $regexp, $matches)) { 30 | if (isset($matches[1]) && $widget->name === $matches[1]) { 31 | $blnTested = false; 32 | $fittedInput = $input; 33 | 34 | switch ($matches[1]) { 35 | case 'vendorname': 36 | $blnTested = true; 37 | $fittedInput = Str::asVendorName($input); 38 | break; 39 | 40 | case 'repositoryname': 41 | $blnTested = true; 42 | $fittedInput = Str::asRepositoryName($input); 43 | break; 44 | 45 | case 'dcatable': 46 | $blnTested = true; 47 | $fittedInput = Str::asContaoDcaTable($input); 48 | break; 49 | 50 | case 'backendmodulecategory': 51 | case 'frontendmodulecategory': 52 | case 'contentelementcategory': 53 | $blnTested = true; 54 | $fittedInput = Str::asSnakeCase($input); 55 | break; 56 | 57 | case 'backendmoduletype': 58 | $blnTested = true; 59 | $fittedInput = Str::asContaoBackendModuleType($input); 60 | break; 61 | 62 | case 'frontendmoduletype': 63 | $blnTested = true; 64 | $fittedInput = Str::asContaoFrontendModuleType($input); 65 | break; 66 | 67 | case 'contentelementtype': 68 | $blnTested = true; 69 | $fittedInput = Str::asContaoContentElementType($input); 70 | break; 71 | 72 | case 'composerdescription': 73 | $blnTested = true; 74 | $fittedInput = Str::asComposerDescription($input); 75 | break; 76 | } 77 | 78 | if ($blnTested && $fittedInput !== $input) { 79 | $widget->addError(\sprintf($GLOBALS['TL_LANG']['ERR']['cbcb_rgxp'], $fittedInput, $input)); 80 | 81 | return true; 82 | } 83 | } 84 | } 85 | 86 | return false; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/BundleMaker/Message/Message.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\BundleMaker\Message; 16 | 17 | use Contao\CoreBundle\Framework\Adapter; 18 | use Contao\CoreBundle\Framework\ContaoFramework; 19 | use Contao\Message as ContaoMessage; 20 | use Symfony\Component\HttpFoundation\RequestStack; 21 | 22 | class Message 23 | { 24 | public const CONTAO_SCOPE = 'BE'; 25 | 26 | public const SESSION_KEY_ERROR = 'contao.BE.error'; 27 | 28 | public const SESSION_KEY_INFO = 'contao.BE.info'; 29 | 30 | public const SESSION_KEY_CONFIRM = 'contao.BE.confirm'; 31 | 32 | protected Adapter $messageAdapter; 33 | 34 | public function __construct( 35 | private readonly ContaoFramework $framework, 36 | private readonly RequestStack $requestStack, 37 | ) { 38 | $this->messageAdapter = $this->framework->getAdapter(ContaoMessage::class); 39 | } 40 | 41 | public function hasInfo(): bool 42 | { 43 | return $this->messageAdapter->hasInfo(static::CONTAO_SCOPE); 44 | } 45 | 46 | public function hasError(): bool 47 | { 48 | return $this->messageAdapter->hasError(static::CONTAO_SCOPE); 49 | } 50 | 51 | public function hasConfirmation(): bool 52 | { 53 | return $this->messageAdapter->hasConfirmation(static::CONTAO_SCOPE); 54 | } 55 | 56 | /** 57 | * Add an info message to the contao backend. 58 | */ 59 | public function addInfo(string $msg): void 60 | { 61 | $this->messageAdapter->addInfo($msg, static::CONTAO_SCOPE); 62 | } 63 | 64 | /** 65 | * Add an error message to the contao backend. 66 | */ 67 | public function addError(string $msg): void 68 | { 69 | $this->messageAdapter->addError($msg, static::CONTAO_SCOPE); 70 | } 71 | 72 | /** 73 | * Add a confirmation message to the contao backend. 74 | */ 75 | public function addConfirmation(string $msg): void 76 | { 77 | $this->messageAdapter->addConfirmation($msg, static::CONTAO_SCOPE); 78 | } 79 | 80 | /** 81 | * Get info messages. 82 | */ 83 | public function getInfo(): array 84 | { 85 | return $this->getFlashMessages(self::SESSION_KEY_INFO); 86 | } 87 | 88 | /** 89 | * Get error messages. 90 | */ 91 | public function getError(): array 92 | { 93 | return $this->getFlashMessages(self::SESSION_KEY_ERROR); 94 | } 95 | 96 | /** 97 | * Get confirmation messages. 98 | */ 99 | public function getConfirmation(): array 100 | { 101 | return $this->getFlashMessages(self::SESSION_KEY_CONFIRM); 102 | } 103 | 104 | /** 105 | * Get flash messages for the contao backend. 106 | */ 107 | private function getFlashMessages(string $type): array 108 | { 109 | $session = $this->requestStack->getCurrentRequest()->getSession(); 110 | $flashBag = $session->getFlashBag(); 111 | 112 | return $flashBag->get($type, []); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/CustomRouteMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 20 | 21 | final class CustomRouteMaker extends AbstractMaker 22 | { 23 | public const PRIORITY = 900; 24 | 25 | public static function getSubscribedEvents(): array 26 | { 27 | return [ 28 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 29 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 30 | ]; 31 | } 32 | 33 | public function addTagsToStorage(AddTagsEvent $event): void 34 | { 35 | parent::addTagsToStorage($event); 36 | 37 | if (!$this->input->addCustomRoute) { 38 | return; 39 | } 40 | 41 | /** @var Str $strAdapter */ 42 | $strAdapter = $this->framework->getAdapter(Str::class); 43 | 44 | $subject = \sprintf( 45 | '%s_%s', 46 | strtolower($this->input->vendorname), 47 | strtolower($this->input->repositoryname), 48 | ); 49 | $subject = preg_replace('/-bundle$/', '', $subject); 50 | $routeId = preg_replace('/-/', '_', $subject); 51 | $this->tagStorage->set('routeid', $routeId); 52 | $this->tagStorage->set('twignamespace', $strAdapter->asTwigNameSpace((string) $this->input->vendorname, (string) $this->input->repositoryname)); 53 | } 54 | 55 | /** 56 | * Add a custom route to the file storage. 57 | * 58 | * @throws \Exception 59 | */ 60 | public function addFilesToStorage(AddMakerEvent $event): void 61 | { 62 | parent::addFilesToStorage($event); 63 | 64 | if (!$this->input->addCustomRoute) { 65 | return; 66 | } 67 | 68 | // Add controller (custom route) 69 | $source = \sprintf( 70 | '%s/src/Controller/Controller.php.ttpl', 71 | $this->skeletonPath, 72 | ); 73 | 74 | $target = \sprintf( 75 | '%s/vendor/%s/%s/src/Controller/MyCustomController.php', 76 | $this->projectDir, 77 | $this->input->vendorname, 78 | $this->input->repositoryname, 79 | ); 80 | 81 | if (!$this->fileStorage->has($target)) { 82 | $this->fileStorage->addFile($source, $target); 83 | } 84 | 85 | // Add twig template 86 | $source = \sprintf( 87 | '%s/templates/MyCustom/my_custom.html.twig.ttpl', 88 | $this->skeletonPath, 89 | ); 90 | 91 | $target = \sprintf( 92 | '%s/vendor/%s/%s/templates/MyCustom/my_custom.html.twig', 93 | $this->projectDir, 94 | $this->input->vendorname, 95 | $this->input->repositoryname, 96 | ); 97 | 98 | if (!$this->fileStorage->has($target)) { 99 | $this->fileStorage->addFile($source, $target); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/FriendlyConfigurationMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 20 | 21 | final class FriendlyConfigurationMaker extends AbstractMaker 22 | { 23 | public const PRIORITY = 890; 24 | 25 | public static function getSubscribedEvents(): array 26 | { 27 | return [ 28 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 29 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 30 | ]; 31 | } 32 | 33 | public function addTagsToStorage(AddTagsEvent $event): void 34 | { 35 | parent::addTagsToStorage($event); 36 | 37 | if (!$this->input->addFriendlyConfiguration) { 38 | return; 39 | } 40 | 41 | /** @var Str $strAdapter */ 42 | $strAdapter = $this->framework->getAdapter(Str::class); 43 | 44 | $this->tagStorage->set('dependencyinjectionextensionclassname', $strAdapter->asDependencyInjectionExtensionClassName((string) $this->input->vendorname, (string) $this->input->repositoryname)); 45 | 46 | $strRootKey = str_replace('Bundle', '', $this->tagStorage->get('toplevelnamespace').$this->tagStorage->get('sublevelnamespace')); 47 | $this->tagStorage->set('friendlyconfigurationrootkey', $strAdapter->asSnakeCase($strRootKey)); 48 | } 49 | 50 | /** 51 | * Add friendly configuration to file storage. 52 | * 53 | * @throws \Exception 54 | */ 55 | public function addFilesToStorage(AddMakerEvent $event): void 56 | { 57 | parent::addFilesToStorage($event); 58 | 59 | if (!$this->input->addFriendlyConfiguration) { 60 | return; 61 | } 62 | 63 | /** @var Str $strAdapter */ 64 | $strAdapter = $this->framework->getAdapter(Str::class); 65 | 66 | $source = \sprintf( 67 | '%s/src/DependencyInjection/Extension.php.ttpl', 68 | $this->skeletonPath, 69 | ); 70 | 71 | $target = \sprintf( 72 | '%s/vendor/%s/%s/src/DependencyInjection/%s.php', 73 | $this->projectDir, 74 | $this->input->vendorname, 75 | $this->input->repositoryname, 76 | $strAdapter->asDependencyInjectionExtensionClassName((string) $this->input->vendorname, (string) $this->input->repositoryname), 77 | ); 78 | 79 | if (!$this->fileStorage->has($target)) { 80 | $this->fileStorage->addFile($source, $target); 81 | } 82 | 83 | $source = \sprintf( 84 | '%s/src/DependencyInjection/Configuration.php.ttpl', 85 | $this->skeletonPath, 86 | ); 87 | 88 | $target = \sprintf( 89 | '%s/vendor/%s/%s/src/DependencyInjection/Configuration.php', 90 | $this->projectDir, 91 | $this->input->vendorname, 92 | $this->input->repositoryname, 93 | ); 94 | 95 | if (!$this->fileStorage->has($target)) { 96 | $this->fileStorage->addFile($source, $target); 97 | } 98 | 99 | $source = \sprintf( 100 | '%s/src/Class.php.ttpl', 101 | $this->skeletonPath, 102 | ); 103 | 104 | $target = \sprintf( 105 | '%s/vendor/%s/%s/src/%s%s.php', 106 | $this->projectDir, 107 | $this->input->vendorname, 108 | $this->input->repositoryname, 109 | $strAdapter->asClassName((string) $this->input->vendorname), 110 | $strAdapter->asClassName((string) $this->input->repositoryname), 111 | ); 112 | 113 | if (!$this->fileStorage->has($target)) { 114 | $this->fileStorage->addFile($source, $target); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /skeleton/src/Controller/FrontendModule/FrontendModuleController.php.ttpl: -------------------------------------------------------------------------------- 1 | page = $page; 46 | 47 | $scopeMatcher = $this->container->get('contao.routing.scope_matcher'); 48 | 49 | if ($this->page instanceof PageModel && $scopeMatcher->isFrontendRequest($request)) { 50 | $this->page->loadDetails(); 51 | } 52 | 53 | return parent::__invoke($request, $model, $section, $classes); 54 | } 55 | 56 | /** 57 | * Lazyload services. 58 | */ 59 | public static function getSubscribedServices(): array 60 | { 61 | $services = parent::getSubscribedServices(); 62 | 63 | $services['contao.framework'] = ContaoFramework::class; 64 | $services['database_connection'] = Connection::class; 65 | $services['contao.routing.scope_matcher'] = ScopeMatcher::class; 66 | $services['translator'] = TranslatorInterface::class; 67 | 68 | return $services; 69 | } 70 | 71 | protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response 72 | { 73 | $userFirstname = 'DUDE'; 74 | $user = $this->tokenStorage->getToken()?->getUser(); 75 | 76 | // Get the logged-in frontend user... if there is one 77 | if ($user instanceof FrontendUser) { 78 | $userFirstname = $user->firstname; 79 | } 80 | 81 | /** @var Session $session */ 82 | $session = $request->getSession(); 83 | $bag = $session->getBag('contao_frontend'); 84 | $bag->set('foo', 'bar'); 85 | 86 | /** @var Date $dateAdapter */ 87 | $dateAdapter = $this->getContaoAdapter(Date::class); 88 | 89 | $intWeekday = $dateAdapter->parse('w'); 90 | $translator = $this->container->get('translator'); 91 | $strWeekday = $translator->trans('DAYS.'.$intWeekday, [], 'contao_default'); 92 | 93 | $guests = []; 94 | 95 | // Get the database connection 96 | $db = $this->container->get('database_connection'); 97 | 98 | /** @var Result $stmt */ 99 | $rows = $db->fetchAllAssociative('SELECT * FROM tl_member WHERE gender = ? ORDER BY lastname', ['female']); 100 | 101 | foreach ($rows as $row) { 102 | $guests[] = $row['firstname']; 103 | } 104 | 105 | $template->set('helloTitle', \sprintf( 106 | 'Hi %s, and welcome to the "Hello World Module". Today is %s.', 107 | $userFirstname, 108 | $strWeekday, 109 | )); 110 | 111 | $template->set('helloText', ''); 112 | 113 | if (!empty($arrGuests)) { 114 | $template->set('helloText', 'Our guests today are: '.implode(', ', $arrGuests)); 115 | } 116 | 117 | return $template->getResponse(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/SessionAttributeBagMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 20 | 21 | final class SessionAttributeBagMaker extends AbstractMaker 22 | { 23 | public const PRIORITY = 1010; 24 | 25 | public static function getSubscribedEvents(): array 26 | { 27 | return [ 28 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 29 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 30 | ]; 31 | } 32 | 33 | public function addTagsToStorage(AddTagsEvent $event): void 34 | { 35 | parent::addTagsToStorage($event); 36 | 37 | if (!$this->input->addSessionAttribute) { 38 | return; 39 | } 40 | 41 | /** @var Str $strAdapter */ 42 | $strAdapter = $this->framework->getAdapter(Str::class); 43 | 44 | $this->tagStorage->set('servicevendornamekey', $strAdapter->asSnakeCase(strtolower((string) $this->input->vendorname))); 45 | $this->tagStorage->set('servicerepositorynamekey', $strAdapter->asSnakeCase(strtolower((string) $this->input->repositoryname))); 46 | 47 | $strRootKey = str_replace('Bundle', '', $this->tagStorage->get('toplevelnamespace').$this->tagStorage->get('sublevelnamespace')); 48 | $this->tagStorage->set('friendlyconfigurationrootkey', $strAdapter->asSnakeCase($strRootKey)); 49 | 50 | $this->tagStorage->set('sessionAttributeName', $strAdapter->asSessionAttributeName(\sprintf('%s_%s', $this->input->vendorname, str_replace('bundle', '', $this->input->repositoryname)))); 51 | $this->tagStorage->set('sessionAttributeKey', '_'.$strAdapter->asSessionAttributeName(\sprintf('%s_%s_attributes', $this->input->vendorname, str_replace('bundle', '', $this->input->repositoryname)))); 52 | $this->tagStorage->set('addSessionAttribute', (string) $this->input->addSessionAttribute); 53 | } 54 | 55 | /** 56 | * Add a custom route to the file storage. 57 | * 58 | * @throws \Exception 59 | */ 60 | public function addFilesToStorage(AddMakerEvent $event): void 61 | { 62 | parent::addFilesToStorage($event); 63 | 64 | if (!$this->input->addSessionAttribute) { 65 | return; 66 | } 67 | 68 | // Add attribute bag 69 | $source = \sprintf( 70 | '%s/src/Session/Attribute/ArrayAttributeBag.php.ttpl', 71 | $this->skeletonPath, 72 | ); 73 | 74 | $target = \sprintf( 75 | '%s/vendor/%s/%s/src/Session/Attribute/ArrayAttributeBag.php', 76 | $this->projectDir, 77 | $this->input->vendorname, 78 | $this->input->repositoryname, 79 | ); 80 | 81 | if (!$this->fileStorage->has($target)) { 82 | $this->fileStorage->addFile($source, $target); 83 | } 84 | 85 | // Add SessionFactory 86 | $source = \sprintf( 87 | '%s/src/Session/SessionFactory.php.ttpl', 88 | $this->skeletonPath, 89 | ); 90 | 91 | $target = \sprintf( 92 | '%s/vendor/%s/%s/src/Session/SessionFactory.php', 93 | $this->projectDir, 94 | $this->input->vendorname, 95 | $this->input->repositoryname, 96 | ); 97 | 98 | if (!$this->fileStorage->has($target)) { 99 | $this->fileStorage->addFile($source, $target); 100 | } 101 | 102 | // Add config/services.yaml 103 | $source = \sprintf( 104 | '%s/config/services.yaml.ttpl', 105 | $this->skeletonPath, 106 | ); 107 | 108 | $target = \sprintf( 109 | '%s/vendor/%s/%s/config/services.yaml', 110 | $this->projectDir, 111 | $this->input->vendorname, 112 | $this->input->repositoryname, 113 | ); 114 | 115 | if (!$this->fileStorage->has($target)) { 116 | $this->fileStorage->addFile($source, $target); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/DataContainer/ContaoBundleCreator.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\DataContainer; 16 | 17 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 18 | use Contao\CoreBundle\Exception\ResponseException; 19 | use Contao\DataContainer; 20 | use Contao\DC_Table; 21 | use Contao\Input; 22 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\BundleMaker; 23 | use Markocupic\ContaoBundleCreatorBundle\Model\ContaoBundleCreatorModel; 24 | use Symfony\Component\Filesystem\Path; 25 | use Symfony\Component\HttpFoundation\RequestStack; 26 | use Symfony\Component\HttpFoundation\Response; 27 | use Symfony\Component\Yaml\Yaml; 28 | 29 | class ContaoBundleCreator 30 | { 31 | public function __construct( 32 | private readonly BundleMaker $bundleMaker, 33 | private readonly RequestStack $requestStack, 34 | private readonly string $projectDir, 35 | ) { 36 | } 37 | 38 | /** 39 | * Launch bundle creator. 40 | * 41 | * @throws \Exception 42 | */ 43 | #[AsCallback(table: 'tl_contao_bundle_creator', target: 'config.onsubmit', priority: 100)] 44 | public function runCreator(DataContainer $dc): void 45 | { 46 | if ('' !== Input::get('id') && '' === Input::post('createBundle') && 'tl_contao_bundle_creator' === Input::post('FORM_SUBMIT') && 'auto' !== Input::post('SUBMIT_TYPE')) { 47 | if (null !== ($objModel = ContaoBundleCreatorModel::findById(Input::get('id')))) { 48 | $this->bundleMaker->run($objModel); 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Download extension as a zip file when clicking on the download button. 55 | */ 56 | #[AsCallback(table: 'tl_contao_bundle_creator', target: 'config.onload', priority: 100)] 57 | public function downloadZipFile(DC_Table $dc): void 58 | { 59 | if ('' !== Input::get('id') && '' === Input::post('downloadBundle') && 'tl_contao_bundle_creator' === Input::post('FORM_SUBMIT') && 'auto' !== Input::post('SUBMIT_TYPE')) { 60 | $session = $this->requestStack->getCurrentRequest()->getSession(); 61 | 62 | if ($session->has('CONTAO-BUNDLE-CREATOR.LAST-ZIP')) { 63 | $zipSrc = $session->get('CONTAO-BUNDLE-CREATOR.LAST-ZIP'); 64 | $session->remove('CONTAO-BUNDLE-CREATOR.LAST-ZIP'); 65 | 66 | $filepath = $this->projectDir.'/'.$zipSrc; 67 | $filename = basename($filepath); 68 | 69 | $response = new Response(); 70 | $response->headers->set('Cache-Control', 'private'); 71 | $response->headers->set('Content-type', 'application/zip'); 72 | $response->headers->set('Content-disposition', 'attachment;filename="'.$filename.'"'); 73 | $response->headers->set('Content-length', (string) filesize($filepath)); 74 | 75 | // Send headers before outputting anything. 76 | $response->sendHeaders(); 77 | $response->setContent((string) readfile($filepath)); 78 | 79 | throw new ResponseException($response); 80 | } 81 | } 82 | } 83 | 84 | #[AsCallback(table: 'tl_contao_bundle_creator', target: 'edit.buttons', priority: 100)] 85 | public function buttonsCallback(array $arrButtons, DC_Table $dc): array 86 | { 87 | if ('edit' === Input::get('act')) { 88 | $arrButtons['createBundle'] = ''; 89 | 90 | $session = $this->requestStack->getCurrentRequest()->getSession(); 91 | 92 | if ($session->has('CONTAO-BUNDLE-CREATOR.LAST-ZIP')) { 93 | $arrButtons['downloadBundle'] = ''; 94 | } 95 | } 96 | 97 | return $arrButtons; 98 | } 99 | 100 | #[AsCallback(table: 'tl_contao_bundle_creator', target: 'fields.composerlicense.options', priority: 100)] 101 | public function getLicenses(): array 102 | { 103 | $data = Yaml::parseFile(Path::join(__DIR__.'/../../config/licenses.yaml')); 104 | 105 | $licenses = []; 106 | 107 | foreach ($data['licenses'] as $k => $v) { 108 | $licenses[$k] = "$k ($v)"; 109 | } 110 | 111 | return $licenses; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /skeleton/contao/dca/tl_sample_table.php.ttpl: -------------------------------------------------------------------------------- 1 | array( 15 | 'dataContainer' => DC_Table::class, 16 | 'enableVersioning' => true, 17 | 'sql' => array( 18 | 'keys' => array( 19 | 'id' => 'primary' 20 | ) 21 | ), 22 | ), 23 | 'list' => array( 24 | 'sorting' => array( 25 | 'mode' => DataContainer::MODE_SORTABLE, 26 | 'fields' => array('title'), 27 | 'flag' => DataContainer::SORT_INITIAL_LETTER_ASC, 28 | 'panelLayout' => 'filter;sort,search,limit' 29 | ), 30 | 'label' => array( 31 | 'fields' => array('title'), 32 | 'format' => '%s', 33 | ), 34 | 'global_operations' => array( 35 | 'all' => array( 36 | 'href' => 'act=select', 37 | 'class' => 'header_edit_all', 38 | 'attributes' => 'data-action="contao--scroll-offset#store"', 39 | ) 40 | ), 41 | 'operations' => array( 42 | 'all', 43 | ) 44 | ), 45 | 'palettes' => array( 46 | '__selector__' => array('addSubpalette'), 47 | 'default' => '{first_legend},title,selectField,checkboxField,multitextField;{second_legend},addSubpalette' 48 | ), 49 | 'subpalettes' => array( 50 | 'addSubpalette' => 'textareaField', 51 | ), 52 | 'fields' => array( 53 | 'id' => array( 54 | 'sql' => "int(10) unsigned NOT NULL auto_increment" 55 | ), 56 | 'tstamp' => array( 57 | 'sql' => "int(10) unsigned NOT NULL default '0'" 58 | ), 59 | 'title' => array( 60 | 'inputType' => 'text', 61 | 'exclude' => true, 62 | 'search' => true, 63 | 'filter' => true, 64 | 'sorting' => true, 65 | 'flag' => DataContainer::SORT_INITIAL_LETTER_ASC, 66 | 'eval' => array('mandatory' => true, 'maxlength' => 255, 'tl_class' => 'w50'), 67 | 'sql' => "varchar(255) NOT NULL default ''" 68 | ), 69 | 'selectField' => array( 70 | 'inputType' => 'select', 71 | 'exclude' => true, 72 | 'search' => true, 73 | 'filter' => true, 74 | 'sorting' => true, 75 | 'reference' => &$GLOBALS['TL_LANG']['{{ dcatable }}'], 76 | 'options' => array('firstoption', 'secondoption'), 77 | //'foreignKey' => 'tl_user.name', 78 | //'options_callback' => array('CLASS', 'METHOD'), 79 | 'eval' => array('includeBlankOption' => true, 'tl_class' => 'w50'), 80 | 'sql' => "varchar(255) NOT NULL default ''", 81 | //'relation' => array('type' => 'hasOne', 'load' => 'lazy') 82 | ), 83 | 'checkboxField' => array( 84 | 'inputType' => 'select', 85 | 'exclude' => true, 86 | 'search' => true, 87 | 'filter' => true, 88 | 'sorting' => true, 89 | 'reference' => &$GLOBALS['TL_LANG']['{{ dcatable }}'], 90 | 'options' => array('firstoption', 'secondoption'), 91 | //'foreignKey' => 'tl_user.name', 92 | //'options_callback' => array('CLASS', 'METHOD'), 93 | 'eval' => array('includeBlankOption' => true, 'chosen' => true, 'tl_class' => 'w50'), 94 | 'sql' => "varchar(255) NOT NULL default ''", 95 | //'relation' => array('type' => 'hasOne', 'load' => 'lazy') 96 | ), 97 | 'multitextField' => array( 98 | 'inputType' => 'text', 99 | 'exclude' => true, 100 | 'search' => true, 101 | 'filter' => true, 102 | 'sorting' => true, 103 | 'eval' => array('multiple' => true, 'size' => 4, 'decodeEntities' => true, 'tl_class' => 'w50'), 104 | 'sql' => "varchar(255) NOT NULL default ''" 105 | ), 106 | 'addSubpalette' => array( 107 | 'exclude' => true, 108 | 'inputType' => 'checkbox', 109 | 'eval' => array('submitOnChange' => true, 'tl_class' => 'w50 clr'), 110 | 'sql' => array('type' => 'boolean', 'default' => false), 111 | ), 112 | 'textareaField' => array( 113 | 'inputType' => 'textarea', 114 | 'exclude' => true, 115 | 'search' => true, 116 | 'filter' => true, 117 | 'sorting' => true, 118 | 'eval' => array('rte' => 'tinyMCE', 'tl_class' => 'clr'), 119 | 'sql' => 'text NULL' 120 | ) 121 | ) 122 | ); 123 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/ComposerJsonMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 19 | 20 | final class ComposerJsonMaker extends AbstractMaker 21 | { 22 | public const PRIORITY = 1000; 23 | 24 | public static function getSubscribedEvents(): array 25 | { 26 | return [ 27 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 28 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 29 | ]; 30 | } 31 | 32 | public function addTagsToStorage(AddTagsEvent $event): void 33 | { 34 | parent::addTagsToStorage($event); 35 | 36 | $this->tagStorage->set('composerdescription', (string) $this->input->composerdescription); 37 | $this->tagStorage->set('composerlicense', (string) $this->input->composerlicense); 38 | $this->tagStorage->set('composerauthorname', (string) $this->input->composerauthorname); 39 | $this->tagStorage->set('composerauthoremail', (string) $this->input->composerauthoremail); 40 | $this->tagStorage->set('composerauthorwebsite', (string) $this->input->composerauthorwebsite); 41 | } 42 | 43 | /** 44 | * Add the composer.json file to file storage. 45 | * 46 | * @throws \Exception 47 | */ 48 | public function addFilesToStorage(AddMakerEvent $event): void 49 | { 50 | parent::addFilesToStorage($event); 51 | 52 | $source = \sprintf( 53 | '%s/composer.json', 54 | $this->skeletonPath, 55 | ); 56 | 57 | $target = \sprintf( 58 | '%s/vendor/%s/%s/composer.json', 59 | $this->projectDir, 60 | $this->input->vendorname, 61 | $this->input->repositoryname, 62 | ); 63 | 64 | if (!$this->fileStorage->has($target)) { 65 | $this->fileStorage->addFile($source, $target); 66 | } 67 | 68 | $content = $this->fileStorage->getContent(); 69 | $objComposer = json_decode($content); 70 | 71 | // Name 72 | $objComposer->name = $this->input->vendorname.'/'.$this->input->repositoryname; 73 | 74 | // Description 75 | $objComposer->description = $this->input->composerdescription; 76 | 77 | // License 78 | $objComposer->license = $this->input->composerlicense; 79 | 80 | // Authors 81 | if (!isset($objComposer->authors) && !\is_array($objComposer->authors)) { 82 | $objComposer->authors = []; 83 | } 84 | $authors = new \stdClass(); 85 | $authors->name = $this->input->composerauthorname; 86 | $authors->email = $this->input->composerauthoremail; 87 | $authors->homepage = $this->input->composerauthorwebsite; 88 | $authors->role = 'Developer'; 89 | $objComposer->authors[] = $authors; 90 | 91 | // Support 92 | if (!isset($objComposer->support) && !\is_object($objComposer->support)) { 93 | $objComposer->support = new \stdClass(); 94 | } 95 | 96 | $objComposer->support->issues = \sprintf( 97 | 'https://github.com/%s/%s/issues', 98 | $this->input->vendorname, 99 | $this->input->repositoryname, 100 | ); 101 | 102 | $objComposer->support->source = \sprintf( 103 | 'https://github.com/%s/%s', 104 | $this->input->vendorname, 105 | $this->input->repositoryname, 106 | ); 107 | 108 | // Version composerpackageversion 109 | if (!empty(trim((string) $this->input->composerpackageversion))) { 110 | $objComposer->version = trim((string) $this->input->composerpackageversion); 111 | } 112 | 113 | // Autoload 114 | if (!isset($objComposer->autoload) && !\is_object($objComposer->autoload)) { 115 | $objComposer->autoload = new \stdClass(); 116 | } 117 | 118 | // Autoload.psr-4 119 | if (!isset($objComposer->autoload->{'psr-4'}) && !\is_object($objComposer->autoload->{'psr-4'})) { 120 | $objComposer->autoload->{'psr-4'} = new \stdClass(); 121 | } 122 | 123 | $psr4Key = \sprintf( 124 | '%s\\%s\\', 125 | $this->tagStorage->get('toplevelnamespace'), 126 | $this->tagStorage->get('sublevelnamespace'), 127 | ); 128 | 129 | $objComposer->autoload->{'psr-4'}->{$psr4Key} = 'src/'; 130 | 131 | // Extra 132 | if (!isset($objComposer->extra) && !\is_object($objComposer->extra)) { 133 | $objComposer->extra = new \stdClass(); 134 | } 135 | 136 | $objComposer->extra->{'contao-manager-plugin'} = \sprintf( 137 | '%s\%s\ContaoManager\Plugin', 138 | $this->tagStorage->get('toplevelnamespace'), 139 | $this->tagStorage->get('sublevelnamespace'), 140 | ); 141 | 142 | $content = json_encode($objComposer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 143 | $this->fileStorage->replaceContent($content); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/AlterRootComposerJsonMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Contao\Date; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 20 | 21 | final class AlterRootComposerJsonMaker extends AbstractMaker 22 | { 23 | public const PRIORITY = -10000; 24 | 25 | public static function getSubscribedEvents(): array 26 | { 27 | return [ 28 | // This subscriber should be executed last!!! 29 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 30 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 31 | ]; 32 | } 33 | 34 | public function addTagsToStorage(AddTagsEvent $event): void 35 | { 36 | parent::addTagsToStorage($event); 37 | } 38 | 39 | /** 40 | * Alter root composer.json. 41 | * 42 | * @throws \Exception 43 | */ 44 | public function addFilesToStorage(AddMakerEvent $event): void 45 | { 46 | parent::addFilesToStorage($event); 47 | 48 | if (!$this->input->editRootComposer) { 49 | return; 50 | } 51 | 52 | $blnModified = false; 53 | $content = file_get_contents($this->projectDir.'/composer.json'); 54 | $objJSON = json_decode($content); 55 | 56 | if (!isset($objJSON->repositories)) { 57 | $objJSON->repositories = []; 58 | } 59 | 60 | $objRepositories = new \stdClass(); 61 | 62 | if ('path' === $this->input->rootcomposerextendrepositorieskey) { 63 | // Check if a package version is set. 64 | if (empty(trim((string) $this->input->composerpackageversion))) { 65 | $this->message->addError('Package version can not be empty if you selected "path" in the "repositories" key of your root composer.json file.'); 66 | 67 | return; 68 | } 69 | 70 | $objRepositories->type = 'path'; 71 | $objRepositories->url = \sprintf( 72 | 'vendor/%s/%s', 73 | $this->input->vendorname, 74 | $this->input->repositoryname, 75 | ); 76 | 77 | // Prevent duplicate entries 78 | if (!\in_array($objRepositories, $objJSON->repositories, false)) { 79 | $blnModified = true; 80 | $objJSON->repositories[] = $objRepositories; 81 | $this->message->addInfo('Extended the repositories section in the root composer.json. Please check!'); 82 | } 83 | } 84 | 85 | if ('vcs-github' === $this->input->rootcomposerextendrepositorieskey) { 86 | $objRepositories->type = 'vcs'; 87 | $objRepositories->url = \sprintf( 88 | 'https://github.com/%s/%s', 89 | $this->input->vendorname, 90 | $this->input->repositoryname, 91 | ); 92 | 93 | // Prevent duplicate entries 94 | if (!\in_array($objRepositories, $objJSON->repositories, false)) { 95 | $blnModified = true; 96 | $objJSON->repositories[] = $objRepositories; 97 | $this->message->addInfo('Extended the repositories section in the root composer.json. Please check!'); 98 | } 99 | } 100 | 101 | // Extend require key 102 | $version = 'dev-main'; 103 | 104 | if (!empty(trim((string) $this->input->composerpackageversion))) { 105 | $version = trim((string) $this->input->composerpackageversion); 106 | } 107 | $objJSON->require->{\sprintf('%s/%s', $this->input->vendorname, $this->input->repositoryname)} = $version; 108 | $this->message->addInfo('Extended the require section in the root composer.json. Please check!'); 109 | $blnModified = true; 110 | 111 | if ($blnModified) { 112 | /** @var Date $dateAdapter */ 113 | $dateAdapter = $this->framework->getAdapter(Date::class); 114 | 115 | // Make a backup first 116 | $strBackupPath = \sprintf( 117 | 'system/tmp/composer_backup_%s.json', 118 | $dateAdapter->parse('Y-m-d _H-i-s', time()), 119 | ); 120 | 121 | copy( 122 | $this->projectDir.\DIRECTORY_SEPARATOR.'composer.json', 123 | $this->projectDir.\DIRECTORY_SEPARATOR.$strBackupPath, 124 | ); 125 | 126 | $this->message->addInfo(\sprintf('Created backup of composer.json in "%s"', $strBackupPath)); 127 | 128 | // Append modifications 129 | $content = json_encode($objJSON, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 130 | 131 | $source = \sprintf( 132 | '%s/composer.json', 133 | $this->projectDir, 134 | ); 135 | 136 | $target = $source; 137 | 138 | if (!$this->fileStorage->has($target)) { 139 | $this->fileStorage->addFile($source, $target); 140 | } 141 | 142 | $this->fileStorage 143 | ->getFile($target) 144 | ->replaceContent($content) 145 | ; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Marko Cupic](docs/logo.png?raw=true "Marko Cupic") 2 | 3 | # Contao Bundle Creator (Boilerplate für eigene Erweiterungen) 4 | 5 | Das Modul ist für Entwickler gedacht, und generiert nach Eingabe einiger Parameter ein Grundgerüst (Boilerplate/Skeleton) für ein Contao 4 Bundle. 6 | 7 | Es können... 8 | - ein Frontendmodul generiert werden. 9 | - ein Backendmodul generiert werden. 10 | - ein Inhaltselement generiert werden. 11 | - eine custom route (https://myhostname.ch/my_custom) generiert werden. 12 | - eine custom session bag generiert werden. 13 | - eine Basisklasse (mit custom root key) für eine friendly configuration generiert werden. 14 | 15 | Alle nötigen Konfigurationsdaten werden automatisch erstellt. 16 | 17 | Falls gewünscht, werden auch die für den Betrieb nötigen Einstellungen in der root composer.json automatisch getätigt. 18 | Nach der Generierung ist es lediglich nötig, 19 | - im Contao Manager einen Updatedurchlauf zu starten und mit dem Installtool die Datenbank upzudaten 20 | - oder per Konsole den `php composer update` und `php vendor/bin/contao-console contao:migrate` Befehl auszuführen 21 | 22 | ## Via Contao Backend das Bundle konfigurieren 23 | 24 | ![Alt text](docs/backend.png?raw=true "Backend") 25 | 26 | ## Verzeichnisstruktur 27 | Folgende Verzeichnisstruktur (ohne Resources Verzeichnis) wird im vendor Ordner angelegt. 28 | 29 | ![Alt text](docs/directory-structure.png?raw=true "Verzeichnisstruktur") 30 | 31 | 32 | ## Inbetriebnahme des Bundles 33 | Nachdem alle Eingaben im Backend gemacht wurden, das Bundle ganz einfach per Knopfdruck generieren lassen. 34 | Die Extension sollte nun im Verzeichnis "vendor" erstellt worden sein und kann auch als ZIP-File heruntergeladen werden. 35 | 36 | ### Variante A (Auch ohne eigenen github-Account möglich) 37 | In der composer.json folgende 2 Einträge machen: 38 | ``` 39 | "repositories": [ 40 | { 41 | "type": "path", 42 | "url": "/home/myhosting/public_html/dirtyharryproductions.com/vendor/dirtyharrycoding/hello-world-bundle" 43 | } 44 | ], 45 | ``` 46 | In der composer.json den **absoluten Pfad** zum Bundle im vendor-Verzeichnis angeben. 47 | Dieser Schritt kann, wenn so eingestellt, von der Erweiterung auch automatisch erledigt werden. 48 | ``` 49 | "require": { 50 | .... 51 | .... 52 | "dirtyharrycoding/hello-world-bundle": "dev-main" 53 | }, 54 | ``` 55 | Im require-Teil das neu erstellte Bundle registrieren. 56 | Dieser Schritt kann, wenn so eingestellt, von der Erweiterung auch automatisch erledigt werden. 57 | 58 | Danach via Contao Manager ein vollständiges Update durchführen und das Installtool aufrufen. Fertig! 59 | 60 | ___ 61 | 62 | ### Variante B 63 | Die Erweiterung auf github.com hochladen und in der composer.json folgende 2 Einträge machen. 64 | ``` 65 | "repositories": [ 66 | { 67 | "type": "vcs", 68 | "url": "https://github.com/dirtyharrycoding/hello-world-bundle" 69 | } 70 | ], 71 | ``` 72 | In der composer.json den Pfad zum github repo angeben. 73 | ``` 74 | "require": { 75 | .... 76 | .... 77 | "dirtyharrycoding/hello-world-bundle": "dev-main" 78 | }, 79 | ``` 80 | Im require-Teil das neu erstellte Bundle registrieren. 81 | 82 | Danach via Contao Manager ein vollständige Update durchführen und das Installtool aufrufen. Fertig! 83 | 84 | Bei Variante B kann es sein, dass github.com die Verbindungsanfrage ablehnt. 85 | Die Erstellung eines **Oauth-Access-Tokens** kann hier Abhilfe schaffen. 86 | Das Access Token muss dann in der **config section** der composer.json im Root eingesetzt werden. 87 | [Github Oauth-Access-Token generieren](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) 88 | ``` 89 | "config": { 90 | "github-oauth": { 91 | "github.com": "43dfdfxxxxxxxxxxxxxxxxxxx5645rtzrfhgfe9" 92 | } 93 | }, 94 | ``` 95 | 96 | ___ 97 | 98 | ### Variante C 99 | Die Erweiterung im Backend via "Bundle herunterladen" Button downloaden und dann im Contao Manager als Paket importieren. 100 | Installtool aufrufen. Fertig! 101 | 102 | ___ 103 | 104 | ### Variante D 105 | Die Erweiterung via github auf packagist.org hochladen und dann via Contao Manager installieren. 106 | Installtool aufrufen. Fertig! 107 | 108 | ## Anmerkungen 109 | * Falls man in den Einstellungen definiert, dass das Skript während der Erstellung des Bundles auch die die root composer.json anpasst, speichert das Skript zur Sicherheit ein Backup der composer.json in system/tmp ab. 110 | * Bei der Erstellung des Bundles wird im Verzeichnis system/tmp zusätzlich ein zip-package mit dem generierten Bundle abgelegt. Das Package kann per Knopfdruck heruntergeladen werden. 111 | 112 | ## Templates updatesicher anpassen 113 | Falls man die Standard-Templates anpassen möchte, die der bundle-maker benötigt, um die PHP-Klassen, Konfigurationsdateien, etc. zu generieren, 114 | kann man seine eigene Templates im Verzeichnis templates/contao-bundle-creator-bundle/skeleton ablegen. 115 | 116 | ![Templates updatesicher überschreiben](docs/custom-templates.png?raw=true "Templates updatesicher überschreiben") 117 | 118 | ## Codefixing mit easy-coding-standard 119 | Auf Wunsch lässt sich "contao/easy-coding-standard" als Abhängigkeit installieren. Bei der Installation werden die Konfigurationsdateien im "vendor/my-custom-bundle/tools/ecs" abgelegt. Der Fixer kann nun so über das Terminal oder über die batch Dateien aufgerufen werden: 120 | 121 | ## App erweitern 122 | Die Bundle-Dateien werden in dieser App über sogenannte "Maker" dem neu zu erstellenden Bundle hinzugefügt. 123 | Mit EventSubscribern können weitere Maker-Klassen hinzugefügt werden. Dazu muss lediglich eine EventSubscriberklasse angelegt werden. 124 | 125 | ## Last but not least 126 | Der Anwender sollte wissen, was er tut ;-) 127 | 128 | Im dümmsten Fall überschreibt man bereits bestehende Erweiterungen und beschädigt so die Installation. 129 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/MiscFilesMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 18 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 19 | 20 | final class MiscFilesMaker extends AbstractMaker 21 | { 22 | public const PRIORITY = 950; 23 | 24 | public static function getSubscribedEvents(): array 25 | { 26 | return [ 27 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 28 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 29 | ]; 30 | } 31 | 32 | public function addTagsToStorage(AddTagsEvent $event): void 33 | { 34 | parent::addTagsToStorage($event); 35 | } 36 | 37 | /** 38 | * Add config files, assets, etc. 39 | * 40 | * @throws \Exception 41 | */ 42 | public function addFilesToStorage(AddMakerEvent $event): void 43 | { 44 | parent::addFilesToStorage($event); 45 | 46 | // config/*.yaml yaml config files 47 | $arrFiles = [ 48 | 'listener.yaml.ttpl', 49 | 'parameters.yaml.ttpl', 50 | 'services.yaml.ttpl', 51 | ]; 52 | 53 | foreach ($arrFiles as $file) { 54 | $source = \sprintf( 55 | '%s/config/%s', 56 | $this->skeletonPath, 57 | $file, 58 | ); 59 | 60 | $target = \sprintf( 61 | '%s/vendor/%s/%s/config/%s', 62 | $this->projectDir, 63 | $this->input->vendorname, 64 | $this->input->repositoryname, 65 | str_replace('.ttpl', '', $file), 66 | ); 67 | 68 | if (!$this->fileStorage->has($target)) { 69 | $this->fileStorage->addFile($source, $target); 70 | } 71 | } 72 | 73 | // src/Resource/contao/config/config.php 74 | $source = \sprintf( 75 | '%s/contao/config/config.php.ttpl', 76 | $this->skeletonPath, 77 | ); 78 | 79 | $target = \sprintf( 80 | '%s/vendor/%s/%s/contao/config/config.php', 81 | $this->projectDir, 82 | $this->input->vendorname, 83 | $this->input->repositoryname, 84 | ); 85 | 86 | if (!$this->fileStorage->has($target)) { 87 | $this->fileStorage->addFile($source, $target); 88 | } 89 | 90 | // Add logo to the docs folder 91 | $source = \sprintf( 92 | '%s/docs/logo.png', 93 | $this->skeletonPath, 94 | ); 95 | 96 | $target = \sprintf( 97 | '%s/vendor/%s/%s/docs/logo.png', 98 | $this->projectDir, 99 | $this->input->vendorname, 100 | $this->input->repositoryname, 101 | ); 102 | 103 | if (!$this->fileStorage->has($target)) { 104 | $this->fileStorage->addFile($source, $target); 105 | } 106 | 107 | // Add empty stylesheet 108 | $source = \sprintf( 109 | '%s/public/css/styles.css', 110 | $this->skeletonPath, 111 | ); 112 | 113 | $target = \sprintf( 114 | '%s/vendor/%s/%s/public/css/styles.css', 115 | $this->projectDir, 116 | $this->input->vendorname, 117 | $this->input->repositoryname, 118 | ); 119 | 120 | if (!$this->fileStorage->has($target)) { 121 | $this->fileStorage->addFile($source, $target); 122 | } 123 | 124 | // Add empty script file 125 | $source = \sprintf( 126 | '%s/public/js/script.js', 127 | $this->skeletonPath, 128 | ); 129 | 130 | $target = \sprintf( 131 | '%s/vendor/%s/%s/public/js/script.js', 132 | $this->projectDir, 133 | $this->input->vendorname, 134 | $this->input->repositoryname, 135 | ); 136 | 137 | if (!$this->fileStorage->has($target)) { 138 | $this->fileStorage->addFile($source, $target); 139 | } 140 | 141 | // Readme.md 142 | $source = \sprintf( 143 | '%s/README.md.ttpl', 144 | $this->skeletonPath, 145 | ); 146 | 147 | $target = \sprintf( 148 | '%s/vendor/%s/%s/README.md', 149 | $this->projectDir, 150 | $this->input->vendorname, 151 | $this->input->repositoryname, 152 | ); 153 | 154 | if (!$this->fileStorage->has($target)) { 155 | $this->fileStorage->addFile($source, $target); 156 | } 157 | 158 | // .editorconfig 159 | $source = \sprintf( 160 | '%s/.editorconfig.txt', 161 | $this->skeletonPath, 162 | ); 163 | 164 | $target = \sprintf( 165 | '%s/vendor/%s/%s/.editorconfig', 166 | $this->projectDir, 167 | $this->input->vendorname, 168 | $this->input->repositoryname, 169 | ); 170 | 171 | if (!$this->fileStorage->has($target)) { 172 | $this->fileStorage->addFile($source, $target); 173 | } 174 | 175 | // .gitattributes 176 | $source = \sprintf( 177 | '%s/.gitattributes.txt', 178 | $this->skeletonPath, 179 | ); 180 | 181 | $target = \sprintf( 182 | '%s/vendor/%s/%s/.gitattributes', 183 | $this->projectDir, 184 | $this->input->vendorname, 185 | $this->input->repositoryname, 186 | ); 187 | 188 | if (!$this->fileStorage->has($target)) { 189 | $this->fileStorage->addFile($source, $target); 190 | } 191 | 192 | // .gitignore 193 | $source = \sprintf( 194 | '%s/.gitignore.txt', 195 | $this->skeletonPath, 196 | ); 197 | 198 | $target = \sprintf( 199 | '%s/vendor/%s/%s/.gitignore', 200 | $this->projectDir, 201 | $this->input->vendorname, 202 | $this->input->repositoryname, 203 | ); 204 | 205 | if (!$this->fileStorage->has($target)) { 206 | $this->fileStorage->addFile($source, $target); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/ContaoContentElementMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Contao\StringUtil; 18 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 20 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 21 | 22 | final class ContaoContentElementMaker extends AbstractMaker 23 | { 24 | public const PRIORITY = 910; 25 | 26 | public static function getSubscribedEvents(): array 27 | { 28 | return [ 29 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 30 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 31 | ]; 32 | } 33 | 34 | public function addTagsToStorage(AddTagsEvent $event): void 35 | { 36 | parent::addTagsToStorage($event); 37 | 38 | if (!$this->input->addContentElement) { 39 | return; 40 | } 41 | 42 | /** @var StringUtil $stringUtilAdapter */ 43 | $stringUtilAdapter = $this->framework->getAdapter(StringUtil::class); 44 | 45 | /** @var Str $strAdapter */ 46 | $strAdapter = $this->framework->getAdapter(Str::class); 47 | 48 | $toplevelnamespace = $strAdapter->asClassName((string) $this->input->vendorname); 49 | $sublevelnamespace = $strAdapter->asClassName((string) $this->input->repositoryname); 50 | $contentelementclassname = $strAdapter->asContaoContentElementClassName((string) $this->input->contentelementtype); 51 | 52 | $this->tagStorage->set('fullyquallifiedcontentelementclassname', \sprintf('%s\%s\Controller\ContentElement\%s', $toplevelnamespace, $sublevelnamespace, $contentelementclassname)); 53 | 54 | $this->tagStorage->set('contentelementclassname', $strAdapter->asContaoContentElementClassName((string) $this->input->contentelementtype)); 55 | $this->tagStorage->set('contentelementtype', (string) $this->input->contentelementtype); 56 | $this->tagStorage->set('contentelementcategory', (string) $this->input->contentelementcategory); 57 | $arrLabel = $stringUtilAdapter->deserialize($this->input->contentelementtrans, true); 58 | $this->tagStorage->set('contentelementtrans_0', $arrLabel[0]); 59 | $this->tagStorage->set('contentelementtrans_1', $arrLabel[1]); 60 | } 61 | 62 | /** 63 | * Add content element files to file storage. 64 | * 65 | * @throws \Exception 66 | */ 67 | public function addFilesToStorage(AddMakerEvent $event): void 68 | { 69 | parent::addFilesToStorage($event); 70 | 71 | if (!$this->input->addContentElement) { 72 | return; 73 | } 74 | 75 | /** @var Str $strAdapter */ 76 | $strAdapter = $this->framework->getAdapter(Str::class); 77 | 78 | // Get the content element template name 79 | $strContentElementTemplateName = $strAdapter->asContaoContentElementTemplateName((string) $this->input->contentelementtype); 80 | 81 | // Get the content element classname 82 | $strContentElementClassname = $strAdapter->asContaoContentElementClassName((string) $this->input->contentelementtype); 83 | 84 | // Add content element class to src/Controller/ContentElement 85 | $source = \sprintf( 86 | '%s/src/Controller/ContentElement/ContentElementController.php.ttpl', 87 | $this->skeletonPath, 88 | ); 89 | 90 | $target = \sprintf( 91 | '%s/vendor/%s/%s/src/Controller/ContentElement/%s.php', 92 | $this->projectDir, 93 | $this->input->vendorname, 94 | $this->input->repositoryname, 95 | $strContentElementClassname, 96 | ); 97 | 98 | if (!$this->fileStorage->has($target)) { 99 | $this->fileStorage->addFile($source, $target); 100 | } 101 | 102 | // Add the content element template 103 | $source = \sprintf( 104 | '%s/contao/templates/twig/content_element/sample_element.html.twig.ttpl', 105 | $this->skeletonPath, 106 | ); 107 | 108 | $target = \sprintf( 109 | '%s/vendor/%s/%s/contao/templates/twig/content_element/%s.html.twig', 110 | $this->projectDir, 111 | $this->input->vendorname, 112 | $this->input->repositoryname, 113 | $strContentElementTemplateName, 114 | ); 115 | 116 | if (!$this->fileStorage->has($target)) { 117 | $this->fileStorage->addFile($source, $target); 118 | } 119 | 120 | // Add the .twig-root file to the content element template directory 121 | $source = \sprintf( 122 | '%s/contao/templates/twig/.twig-root', 123 | $this->skeletonPath, 124 | ); 125 | 126 | $target = \sprintf( 127 | '%s/vendor/%s/%s/contao/templates/twig/.twig-root', 128 | $this->projectDir, 129 | $this->input->vendorname, 130 | $this->input->repositoryname, 131 | ); 132 | 133 | if (!$this->fileStorage->has($target)) { 134 | $this->fileStorage->addFile($source, $target); 135 | } 136 | 137 | // Add contao/dca/tl_content.php 138 | $source = \sprintf( 139 | '%s/contao/dca/tl_content.php.ttpl', 140 | $this->skeletonPath, 141 | ); 142 | 143 | $target = \sprintf( 144 | '%s/vendor/%s/%s/contao/dca/tl_content.php', 145 | $this->projectDir, 146 | $this->input->vendorname, 147 | $this->input->repositoryname, 148 | ); 149 | 150 | if (!$this->fileStorage->has($target)) { 151 | $this->fileStorage->addFile($source, $target); 152 | } 153 | 154 | // Add contao/languages/en/modules.php to file storage 155 | $target = \sprintf( 156 | '%s/vendor/%s/%s/contao/languages/en/default.php', 157 | $this->projectDir, 158 | $this->input->vendorname, 159 | $this->input->repositoryname, 160 | ); 161 | 162 | $source = \sprintf( 163 | '%s/contao/languages/en/default.php.ttpl', 164 | $this->skeletonPath, 165 | ); 166 | 167 | if (!$this->fileStorage->has($target)) { 168 | $this->fileStorage->addFile($source, $target); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/ContaoBackendModuleMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Contao\Controller; 18 | use Contao\StringUtil; 19 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 20 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 21 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 22 | 23 | final class ContaoBackendModuleMaker extends AbstractMaker 24 | { 25 | public const PRIORITY = 930; 26 | 27 | public static function getSubscribedEvents(): array 28 | { 29 | return [ 30 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 31 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 32 | ]; 33 | } 34 | 35 | public function addTagsToStorage(AddTagsEvent $event): void 36 | { 37 | parent::addTagsToStorage($event); 38 | 39 | if (!$this->input->addBackendModule || empty($this->input->dcatable)) { 40 | return; 41 | } 42 | 43 | /** @var Controller $controllerAdapter */ 44 | $controllerAdapter = $this->framework->getAdapter(Controller::class); 45 | 46 | /** @var StringUtil $stringUtilAdapter */ 47 | $stringUtilAdapter = $this->framework->getAdapter(StringUtil::class); 48 | 49 | /** @var Str $strAdapter */ 50 | $strAdapter = $this->framework->getAdapter(Str::class); 51 | 52 | $controllerAdapter->loadDataContainer($this->input->dcatable); 53 | 54 | $this->tagStorage->set('dcaclassname', $strAdapter->asDcaClassName((string) $this->input->dcatable)); 55 | $this->tagStorage->set('dcatable', (string) $this->input->dcatable); 56 | $this->tagStorage->set('modelclassname', (string) $strAdapter->asContaoModelClassName((string) $this->input->dcatable)); 57 | $this->tagStorage->set('backendmoduletype', (string) $this->input->backendmoduletype); 58 | $this->tagStorage->set('backendmodulecategory', (string) $this->input->backendmodulecategory); 59 | $arrLabel = $stringUtilAdapter->deserialize($this->input->backendmoduletrans, true); 60 | $this->tagStorage->set('backendmoduletrans_0', $arrLabel[0]); 61 | $this->tagStorage->set('backendmoduletrans_1', $arrLabel[1]); 62 | } 63 | 64 | /** 65 | * Add backend module files to file storage. 66 | * 67 | * @throws \Exception 68 | */ 69 | public function addFilesToStorage(AddMakerEvent $event): void 70 | { 71 | parent::addFilesToStorage($event); 72 | 73 | if (!$this->input->addBackendModule || empty($this->input->dcatable)) { 74 | return; 75 | } 76 | 77 | /** @var Str $strAdapter */ 78 | $strAdapter = $this->framework->getAdapter(Str::class); 79 | 80 | // Add dca table file 81 | $source = \sprintf( 82 | '%s/contao/dca/tl_sample_table.php.ttpl', 83 | $this->skeletonPath, 84 | ); 85 | 86 | $target = \sprintf( 87 | '%s/vendor/%s/%s/contao/dca/%s.php', 88 | $this->projectDir, 89 | $this->input->vendorname, 90 | $this->input->repositoryname, 91 | $this->input->dcatable, 92 | ); 93 | 94 | if (!$this->fileStorage->has($target)) { 95 | $this->fileStorage->addFile($source, $target); 96 | } 97 | 98 | // Add dca class 99 | $source = \sprintf( 100 | '%s/src/DataContainer/DcaClass.php.ttpl', 101 | $this->skeletonPath, 102 | ); 103 | 104 | $target = \sprintf( 105 | '%s/vendor/%s/%s/src/DataContainer/%s.php', 106 | $this->projectDir, 107 | $this->input->vendorname, 108 | $this->input->repositoryname, 109 | $strAdapter->asDcaClassName((string) $this->input->dcatable), 110 | ); 111 | 112 | if (!$this->fileStorage->has($target)) { 113 | $this->fileStorage->addFile($source, $target); 114 | } 115 | 116 | // Add dca table translation file 117 | $source = \sprintf( 118 | '%s/contao/languages/en/tl_sample_table.php.ttpl', 119 | $this->skeletonPath, 120 | ); 121 | 122 | $target = \sprintf( 123 | '%s/vendor/%s/%s/contao/languages/en/%s.php', 124 | $this->projectDir, 125 | $this->input->vendorname, 126 | $this->input->repositoryname, 127 | $this->input->dcatable, 128 | ); 129 | 130 | if (!$this->fileStorage->has($target)) { 131 | $this->fileStorage->addFile($source, $target); 132 | } 133 | 134 | // Add a sample model 135 | $source = \sprintf( 136 | '%s/src/Model/Model.php.ttpl', 137 | $this->skeletonPath, 138 | ); 139 | 140 | $target = \sprintf( 141 | '%s/vendor/%s/%s/src/Model/%s.php', 142 | $this->projectDir, 143 | $this->input->vendorname, 144 | $this->input->repositoryname, 145 | $strAdapter->asContaoModelClassName((string) $this->input->dcatable), 146 | ); 147 | 148 | if (!$this->fileStorage->has($target)) { 149 | $this->fileStorage->addFile($source, $target); 150 | } 151 | 152 | // Add contao/languages/en/modules.php to file storage 153 | $target = \sprintf( 154 | '%s/vendor/%s/%s/contao/languages/en/modules.php', 155 | $this->projectDir, 156 | $this->input->vendorname, 157 | $this->input->repositoryname, 158 | ); 159 | 160 | $source = \sprintf( 161 | '%s/contao/languages/en/modules.php.ttpl', 162 | $this->skeletonPath, 163 | ); 164 | 165 | if (!$this->fileStorage->has($target)) { 166 | $this->fileStorage->addFile($source, $target); 167 | } 168 | 169 | // Add contao/languages/en/default.php to file storage 170 | $target = \sprintf( 171 | '%s/vendor/%s/%s/contao/languages/en/default.php', 172 | $this->projectDir, 173 | $this->input->vendorname, 174 | $this->input->repositoryname, 175 | ); 176 | 177 | $source = \sprintf( 178 | '%s/contao/languages/en/default.php.ttpl', 179 | $this->skeletonPath, 180 | ); 181 | 182 | if (!$this->fileStorage->has($target)) { 183 | $this->fileStorage->addFile($source, $target); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/EventSubscriber/Maker/ContaoFrontendModuleMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\EventSubscriber\Maker; 16 | 17 | use Contao\StringUtil; 18 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str\Str; 19 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 20 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 21 | 22 | final class ContaoFrontendModuleMaker extends AbstractMaker 23 | { 24 | public const PRIORITY = 920; 25 | 26 | public static function getSubscribedEvents(): array 27 | { 28 | return [ 29 | AddTagsEvent::NAME => ['addTagsToStorage', self::PRIORITY], 30 | AddMakerEvent::NAME => ['addFilesToStorage', self::PRIORITY], 31 | ]; 32 | } 33 | 34 | public function addTagsToStorage(AddTagsEvent $event): void 35 | { 36 | parent::addTagsToStorage($event); 37 | 38 | if (!$this->input->addFrontendModule) { 39 | return; 40 | } 41 | 42 | /** @var Str $strAdapter */ 43 | $strAdapter = $this->framework->getAdapter(Str::class); 44 | 45 | $stringUtilAdaper = $this->framework->getAdapter(StringUtil::class); 46 | 47 | $toplevelnamespace = $strAdapter->asClassName((string) $this->input->vendorname); 48 | $sublevelnamespace = $strAdapter->asClassName((string) $this->input->repositoryname); 49 | $frontendmoduleclassname = $strAdapter->asContaoFrontendModuleClassName((string) $this->input->frontendmoduletype); 50 | 51 | $this->tagStorage->set('fullyquallifiedfrontendmoduleclassname', \sprintf('%s\%s\Controller\FrontendModule\%s', $toplevelnamespace, $sublevelnamespace, $frontendmoduleclassname)); 52 | $this->tagStorage->set('frontendmoduleclassname', $frontendmoduleclassname); 53 | $this->tagStorage->set('frontendmoduletype', (string) $this->input->frontendmoduletype); 54 | $this->tagStorage->set('frontendmodulecategory', (string) $this->input->frontendmodulecategory); 55 | $arrLabel = $stringUtilAdaper->deserialize($this->input->frontendmoduletrans, true); 56 | $this->tagStorage->set('frontendmoduletrans_0', $arrLabel[0]); 57 | $this->tagStorage->set('frontendmoduletrans_1', $arrLabel[1]); 58 | } 59 | 60 | /** 61 | * Add frontend module files to file storage. 62 | * 63 | * @throws \Exception 64 | */ 65 | public function addFilesToStorage(AddMakerEvent $event): void 66 | { 67 | parent::addFilesToStorage($event); 68 | 69 | if (!$this->input->addFrontendModule) { 70 | return; 71 | } 72 | 73 | /** @var Str $strAdapter */ 74 | $strAdapter = $this->framework->getAdapter(Str::class); 75 | 76 | // Get the frontend module template name 77 | $strFrontenModuleTemplateName = $strAdapter->asContaoFrontendModuleTemplateName((string) $this->input->frontendmoduletype); 78 | 79 | // Get the frontend module classname 80 | $strFrontendModuleClassname = $strAdapter->asContaoFrontendModuleClassName((string) $this->input->frontendmoduletype); 81 | 82 | // Add the frontend module class to src/Controller/FrontendModuleController 83 | $source = \sprintf( 84 | '%s/src/Controller/FrontendModule/FrontendModuleController.php.ttpl', 85 | $this->skeletonPath, 86 | ); 87 | 88 | $target = \sprintf( 89 | '%s/vendor/%s/%s/src/Controller/FrontendModule/%s.php', 90 | $this->projectDir, 91 | $this->input->vendorname, 92 | $this->input->repositoryname, 93 | $strFrontendModuleClassname, 94 | ); 95 | 96 | if (!$this->fileStorage->has($target)) { 97 | $this->fileStorage->addFile($source, $target); 98 | } 99 | 100 | // Add the content element template 101 | $source = \sprintf( 102 | '%s/contao/templates/twig/frontend_module/sample_module.html.twig.ttpl', 103 | $this->skeletonPath, 104 | ); 105 | 106 | $target = \sprintf( 107 | '%s/vendor/%s/%s/contao/templates/twig/frontend_module/%s.html.twig', 108 | $this->projectDir, 109 | $this->input->vendorname, 110 | $this->input->repositoryname, 111 | $strFrontenModuleTemplateName, 112 | ); 113 | 114 | if (!$this->fileStorage->has($target)) { 115 | $this->fileStorage->addFile($source, $target); 116 | } 117 | 118 | // Add the .twig-root file to the content element template directory 119 | $source = \sprintf( 120 | '%s/contao/templates/twig/.twig-root', 121 | $this->skeletonPath, 122 | ); 123 | 124 | $target = \sprintf( 125 | '%s/vendor/%s/%s/contao/templates/twig/.twig-root', 126 | $this->projectDir, 127 | $this->input->vendorname, 128 | $this->input->repositoryname, 129 | ); 130 | 131 | if (!$this->fileStorage->has($target)) { 132 | $this->fileStorage->addFile($source, $target); 133 | } 134 | 135 | // Add contao/dca/tl_module.php 136 | $source = \sprintf( 137 | '%s/contao/dca/tl_module.php.ttpl', 138 | $this->skeletonPath, 139 | ); 140 | 141 | $target = \sprintf( 142 | '%s/vendor/%s/%s/contao/dca/tl_module.php', 143 | $this->projectDir, 144 | $this->input->vendorname, 145 | $this->input->repositoryname, 146 | ); 147 | 148 | if (!$this->fileStorage->has($target)) { 149 | $this->fileStorage->addFile($source, $target); 150 | } 151 | 152 | // Add contao/languages/en/modules.php to file storage 153 | $target = \sprintf( 154 | '%s/vendor/%s/%s/contao/languages/en/modules.php', 155 | $this->projectDir, 156 | $this->input->vendorname, 157 | $this->input->repositoryname, 158 | ); 159 | 160 | $source = \sprintf( 161 | '%s/contao/languages/en/modules.php.ttpl', 162 | $this->skeletonPath, 163 | ); 164 | 165 | if (!$this->fileStorage->has($target)) { 166 | $this->fileStorage->addFile($source, $target); 167 | } 168 | 169 | // Add contao/languages/en/default.php to file storage 170 | $target = \sprintf( 171 | '%s/vendor/%s/%s/contao/languages/en/default.php', 172 | $this->projectDir, 173 | $this->input->vendorname, 174 | $this->input->repositoryname, 175 | ); 176 | 177 | $source = \sprintf( 178 | '%s/contao/languages/en/default.php.ttpl', 179 | $this->skeletonPath, 180 | ); 181 | 182 | if (!$this->fileStorage->has($target)) { 183 | $this->fileStorage->addFile($source, $target); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/BundleMaker/BundleMaker.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\BundleMaker; 16 | 17 | use Contao\CoreBundle\Framework\ContaoFramework; 18 | use Contao\Date; 19 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Message\Message; 20 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage\FileStorage; 21 | use Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage\TagStorage; 22 | use Markocupic\ContaoBundleCreatorBundle\Event\AddMakerEvent; 23 | use Markocupic\ContaoBundleCreatorBundle\Event\AddTagsEvent; 24 | use Markocupic\ContaoBundleCreatorBundle\Model\ContaoBundleCreatorModel; 25 | use Markocupic\ContaoBundleCreatorBundle\Skeleton; 26 | use Markocupic\ZipBundle\Zip\Zip; 27 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 28 | use Symfony\Component\HttpFoundation\RequestStack; 29 | use Symfony\Component\Yaml\Yaml; 30 | 31 | class BundleMaker 32 | { 33 | protected ContaoBundleCreatorModel|null $input = null; 34 | 35 | public function __construct( 36 | private readonly ContaoFramework $framework, 37 | private readonly RequestStack $requestStack, 38 | private readonly FileStorage $fileStorage, 39 | private readonly TagStorage $tagStorage, 40 | private readonly EventDispatcherInterface $eventDispatcher, 41 | private readonly Message $message, 42 | private readonly Zip $zip, 43 | private readonly string $projectDir, 44 | ) { 45 | } 46 | 47 | /** 48 | * Run the contao bundle creator. 49 | * 50 | * @throws \Exception 51 | */ 52 | public function run(ContaoBundleCreatorModel $input): void 53 | { 54 | $this->input = $input; 55 | 56 | if ($this->bundleExists() && !$this->input->overwriteexisting) { 57 | $this->message->addError('An extension with the same name already exists. Please set the "override extension flag".'); 58 | 59 | return; 60 | } 61 | 62 | // Create a backup of the old bundle that will be overwritten now 63 | if ($this->bundleExists()) { 64 | $this->createBackup(); 65 | } 66 | 67 | $this->message->addInfo(\sprintf('Started generating "%s/%s" bundle.', $this->input->vendorname, $this->input->repositoryname)); 68 | 69 | /* 70 | * Keep the application extensible. 71 | * Write maker classes to add tags & files to the bundle. 72 | * Store maker classes in src/EventSubscriber/Maker and 73 | * implement these makers as event subscribers. 74 | * 75 | * 1. Add all the necessary tags to the tag storage. 76 | */ 77 | $event = new AddTagsEvent($this->framework, $this->requestStack, $this->tagStorage, $this->fileStorage, $this->input, $this->message, Skeleton::getDefaultPath(), $this->projectDir); 78 | $this->eventDispatcher->dispatch($event, AddTagsEvent::NAME); 79 | 80 | /* 81 | * 2. Add all the files to a virtual file storage. 82 | */ 83 | $event = new AddMakerEvent($this->framework, $this->requestStack, $this->tagStorage, $this->fileStorage, $this->input, $this->message, Skeleton::getDefaultPath(), $this->projectDir); 84 | $this->eventDispatcher->dispatch($event, AddMakerEvent::NAME); 85 | 86 | /* 87 | * 3. Replace tags in the file storage. 88 | */ 89 | $this->replaceTags(); 90 | 91 | /* 92 | * 4. Check yaml files. 93 | */ 94 | $this->checkYamlFiles(); 95 | 96 | /* 97 | * 5. Copy all the bundle files from the virtual storage to the destination directories in vendor/vendorname/bundlename. 98 | */ 99 | $this->writeBundleFiles(); 100 | 101 | /* 102 | * 6. Store the new bundle also as a zip-package in system/tmp for downloading it after the generating process. 103 | */ 104 | $this->generateZipArchive(); 105 | } 106 | 107 | /** 108 | * Check if an extension with the same name already exists. 109 | */ 110 | protected function bundleExists(): bool 111 | { 112 | return is_dir($this->projectDir.'/vendor/'.$this->input->vendorname.'/'.$this->input->repositoryname); 113 | } 114 | 115 | protected function createBackup(): void 116 | { 117 | $zipSource = \sprintf( 118 | '%s/vendor/%s/%s', 119 | $this->projectDir, 120 | $this->input->vendorname, 121 | $this->input->repositoryname, 122 | ); 123 | 124 | $zipTarget = \sprintf( 125 | '%s/system/tmp/%s.zip', 126 | $this->projectDir, 127 | $this->input->repositoryname.'_backup_'.Date::parse('Y-m-d_H-i-s', time()), 128 | ); 129 | 130 | $this->zip 131 | ->stripSourcePath($zipSource) 132 | ->addDirRecursive($zipSource) 133 | ->run($zipTarget) 134 | ; 135 | } 136 | 137 | protected function generateZipArchive(): void 138 | { 139 | // Do not create the bundle if there is an error. 140 | if ($this->message->hasError()) { 141 | return; 142 | } 143 | 144 | // Store the new bundle also as a zip-package in system/tmp for downloading it after the generating process 145 | $zipSource = \sprintf( 146 | '%s/vendor/%s/%s', 147 | $this->projectDir, 148 | $this->input->vendorname, 149 | $this->input->repositoryname, 150 | ); 151 | 152 | $zipTarget = \sprintf( 153 | '%s/system/tmp/%s-main.zip', 154 | $this->projectDir, 155 | $this->input->repositoryname, 156 | ); 157 | 158 | $zip = $this->zip 159 | ->ignoreDotFiles(false) 160 | ->stripSourcePath($this->projectDir.'/vendor/'.$this->input->vendorname) 161 | ->addDirRecursive($zipSource) 162 | ; 163 | 164 | if ($zip->run($zipTarget)) { 165 | $session = $this->requestStack->getCurrentRequest()->getSession(); 166 | $session->set('CONTAO-BUNDLE-CREATOR.LAST-ZIP', str_replace($this->projectDir.'/', '', $zipTarget)); 167 | } 168 | } 169 | 170 | /** 171 | * Replace tags in file storage. 172 | */ 173 | protected function replaceTags(): void 174 | { 175 | // Do not create the bundle if there is an error. 176 | if ($this->message->hasError()) { 177 | return; 178 | } 179 | 180 | foreach ($this->fileStorage->getAll() as $arrFile) { 181 | if ($this->fileStorage->has($arrFile['target'])) { 182 | $this->fileStorage 183 | ->getFile($arrFile['target']) 184 | ->replaceTags($this->tagStorage, ['ttpl']) 185 | ; 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * @throws \Exception 192 | */ 193 | protected function checkYamlFiles(): void 194 | { 195 | // Do not create the bundle if there is an error. 196 | if ($this->message->hasError()) { 197 | return; 198 | } 199 | 200 | /** @var Yaml $yamlAdapter */ 201 | $yamlAdapter = $this->framework->getAdapter(Yaml::class); 202 | 203 | foreach ($this->fileStorage->getAll() as $arrFile) { 204 | if ($this->fileStorage->has($arrFile['target'])) { 205 | $info = new \SplFileInfo($arrFile['target']); 206 | 207 | if ('yaml' === $info->getExtension() || 'yml' === $info->getExtension()) { 208 | $strYaml = $this->fileStorage 209 | ->getFile($arrFile['target']) 210 | ->getContent() 211 | ; 212 | 213 | // Validate yaml files 214 | $yamlAdapter->parse($strYaml); 215 | } 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * Write files from the file storage to the filesystem. 222 | */ 223 | protected function writeBundleFiles(): void 224 | { 225 | // Do not generate the bundle if there is an error. 226 | if ($this->message->hasError()) { 227 | return; 228 | } 229 | 230 | foreach ($this->fileStorage->getAll() as $arrFile) { 231 | try { 232 | $this->fileStorage->createFile($arrFile['target']); 233 | $this->message->addInfo(\sprintf('Created file "%s".', $arrFile['target'])); 234 | } catch (\Exception $e) { 235 | // Display a message in the backend 236 | $this->message->addError(\sprintf('Could not create file "%s".', $arrFile['target'])); 237 | } 238 | } 239 | 240 | // Display message in the backend 241 | $this->message->addConfirmation('Added one or more files to the bundle. Please run at least "composer install" or even "composer update", if you have made changes to the root composer.json.'); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/BundleMaker/Storage/FileStorage.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\BundleMaker\Storage; 16 | 17 | use Markocupic\ContaoBundleCreatorBundle\Skeleton; 18 | use Symfony\Component\Filesystem\Exception\FileNotFoundException; 19 | use Symfony\Component\Filesystem\Filesystem; 20 | use Symfony\Component\Filesystem\Path; 21 | use Symfony\Component\Finder\Finder; 22 | use Twig\Environment; 23 | 24 | /** 25 | * Usage:. 26 | * 27 | * $fileStorage = new FileStorage(); 28 | * 29 | * $fileStorage 30 | * ->addFile('somefolder/somefile.txt', 'destination/somefile.txt') 31 | * ->appendContent('bla, bla'); 32 | * 33 | * or: 34 | * // Override file 35 | * $fileStorage 36 | * ->addFile('somefolder/somefile.txt', 'destination/somefile.txt', true); 37 | * 38 | * or: 39 | * // Create a new file from string 40 | * $fileStorage 41 | * ->addFileFromString('destination/somefile.txt', 'Lorem ipsum',); 42 | * 43 | * or: 44 | * 45 | * if($fileStorage->has('somefolder/someotherfile.txt')) 46 | * { 47 | * $fileStorage 48 | * ->getFile('somefolder/someotherfile.txt') 49 | * ->truncate() 50 | * ->appendContent('bla, bla'); 51 | * } 52 | */ 53 | class FileStorage 54 | { 55 | protected array $storage = []; 56 | 57 | protected int $intIndex = -1; 58 | 59 | public function __construct( 60 | private readonly Environment $twig, 61 | ) { 62 | } 63 | 64 | /** 65 | * @throws \Exception 66 | */ 67 | public function addFile(string $sourcePath, string $targetPath, bool $forceOverride = false): self 68 | { 69 | $sourcePath = Path::canonicalize($sourcePath); 70 | $targetPath = Path::canonicalize($targetPath); 71 | 72 | if (!is_file($sourcePath)) { 73 | throw new FileNotFoundException(\sprintf('File "%s" not found.', $sourcePath)); 74 | } 75 | 76 | if ($this->has($targetPath) && !$forceOverride) { 77 | throw new \Exception(\sprintf('File "%s" is already set. Please use the $forceOverride parameter or call FileStorage::getFile()->replaceContent() instead.', $targetPath)); 78 | } 79 | 80 | // Replace the default source with a custom source 81 | // stored in the "templates/contao-bundle-creator-bundle/skeleton" directory 82 | $search = Skeleton::getDefaultPath(); 83 | $replace = Skeleton::getCustomTemplatePath(); 84 | 85 | $customSourcePath = str_replace($search, $replace, $sourcePath); 86 | 87 | if (is_file($customSourcePath)) { 88 | $sourcePath = $customSourcePath; 89 | } 90 | 91 | $data = [ 92 | 'source' => $sourcePath, 93 | 'target' => $targetPath, 94 | 'content' => file_get_contents($sourcePath), 95 | ]; 96 | 97 | if (($index = $this->getIndexOf($targetPath)) < 0) { 98 | $this->storage[] = $data; 99 | } else { 100 | $this->storage[$index] = $data; 101 | } 102 | 103 | $this->intIndex = $this->getIndexOf($targetPath); 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * @throws \Exception 110 | */ 111 | public function addFilesFromFolder(string $sourcePath, string $targetPath, bool $traverseSubdirectories = false, bool $forceOverride = false): array 112 | { 113 | $sourcePath = Path::canonicalize($sourcePath); 114 | $targetPath = Path::canonicalize($targetPath); 115 | 116 | if (!is_dir($sourcePath)) { 117 | throw new FileNotFoundException(\sprintf('Folder "%s" not found.', $sourcePath)); 118 | } 119 | 120 | $finder = new Finder(); 121 | 122 | if (false === $traverseSubdirectories) { 123 | $finder->depth('== 0'); 124 | } 125 | 126 | $arrFiles = []; 127 | 128 | foreach ($finder->files()->ignoreDotFiles(false)->in($sourcePath) as $file) { 129 | $relPath = Path::makeRelative($file->getRealPath(), $sourcePath); 130 | 131 | if ('ttpl' === $file->getExtension()) { 132 | // Remove the .ttpl extension from the file path 133 | $relPath = preg_replace('/\.ttpl$/', '', $relPath); 134 | } 135 | 136 | $this->addFile($file->getRealPath(), Path::join($targetPath, $relPath), $forceOverride); 137 | $arrFiles[] = Path::join($targetPath, $relPath); 138 | } 139 | 140 | return $arrFiles; 141 | } 142 | 143 | /** 144 | * @throws \Exception 145 | */ 146 | public function addFileFromString(string $targetPath, string $content = '', bool $forceOverride = false): self 147 | { 148 | $targetPath = Path::canonicalize($targetPath); 149 | 150 | if ($this->has($targetPath) && !$forceOverride) { 151 | throw new \Exception(\sprintf('File "%s" is already set. Please use FileStorage::getFile()->replaceContent() instead.', $targetPath)); 152 | } 153 | 154 | $data = [ 155 | 'source' => null, 156 | 'target' => $targetPath, 157 | 'content' => $content, 158 | ]; 159 | 160 | if (($index = $this->getIndexOf($targetPath)) < 0) { 161 | $this->storage[] = $data; 162 | } else { 163 | $this->storage[$index] = $data; 164 | } 165 | 166 | $this->intIndex = $this->getIndexOf($targetPath); 167 | 168 | return $this; 169 | } 170 | 171 | /** 172 | * @throws \Exception 173 | */ 174 | public function getFile(string $targetPath): self 175 | { 176 | $targetPath = Path::canonicalize($targetPath); 177 | 178 | if (($index = $this->getIndexOf($targetPath)) < 0) { 179 | throw new \Exception(\sprintf('File "%s" not found in the storage', $targetPath)); 180 | } 181 | 182 | $this->intIndex = $index; 183 | 184 | return $this; 185 | } 186 | 187 | public function has(string $targetPath): bool 188 | { 189 | $targetPath = Path::canonicalize($targetPath); 190 | 191 | if ($this->getIndexOf($targetPath) < 0) { 192 | return false; 193 | } 194 | 195 | return true; 196 | } 197 | 198 | public function removeFile(): self 199 | { 200 | if ($this->intIndex > -1) { 201 | if (isset($this->storage[$this->intIndex])) { 202 | unset($this->storage[$this->intIndex]); 203 | } 204 | } 205 | 206 | $this->intIndex = -1; 207 | 208 | return $this; 209 | } 210 | 211 | public function removeAll(): self 212 | { 213 | $this->storage = []; 214 | $this->intIndex = -1; 215 | 216 | return $this; 217 | } 218 | 219 | /** 220 | * @throws \Exception 221 | */ 222 | public function appendContent(string $strContent): self 223 | { 224 | if ($this->intIndex < 0) { 225 | throw $this->sendFilePointerNotSetException(); 226 | } 227 | 228 | $this->storage[$this->intIndex]['content'] .= $strContent; 229 | 230 | return $this; 231 | } 232 | 233 | /** 234 | * @throws \Exception 235 | */ 236 | public function replaceContent(string $strContent): self 237 | { 238 | if ($this->intIndex < 0) { 239 | throw $this->sendFilePointerNotSetException(); 240 | } 241 | 242 | $this->storage[$this->intIndex]['content'] = $strContent; 243 | 244 | return $this; 245 | } 246 | 247 | /** 248 | * @throws \Exception 249 | */ 250 | public function getContent(): string 251 | { 252 | if ($this->intIndex < 0) { 253 | throw $this->sendFilePointerNotSetException(); 254 | } 255 | 256 | return (string) $this->storage[$this->intIndex]['content']; 257 | } 258 | 259 | /** 260 | * @throws \Exception 261 | */ 262 | public function truncate(): self 263 | { 264 | if ($this->intIndex < 0) { 265 | throw $this->sendFilePointerNotSetException(); 266 | } 267 | 268 | $this->storage[$this->intIndex]['content'] = ''; 269 | 270 | return $this; 271 | } 272 | 273 | public function getAll(): array 274 | { 275 | return $this->storage; 276 | } 277 | 278 | /** 279 | * Replace tags. 280 | * 281 | * @throws \Exception 282 | */ 283 | public function replaceTags(TagStorage $tagStorage, array $extensions = []): self 284 | { 285 | if ($this->intIndex < 0) { 286 | throw $this->sendFilePointerNotSetException(); 287 | } 288 | 289 | $blnReplace = true; 290 | 291 | if (\count($extensions) > 0) { 292 | $blnReplace = false; 293 | 294 | foreach ($extensions as $extension) { 295 | if (isset($this->storage[$this->intIndex]['source'])) { 296 | if (empty($this->storage[$this->intIndex]['source'])) { 297 | continue; 298 | } 299 | $file = new \SplFileObject($this->storage[$this->intIndex]['source'], 'rb'); // 'rb' = read binary safe 300 | if ($file->getExtension() === $extension) { 301 | $blnReplace = true; 302 | } 303 | } 304 | } 305 | } 306 | 307 | if ($blnReplace) { 308 | if ($this->isTemplate($file)) { 309 | $content = file_get_contents($file->getRealPath()); 310 | $this->storage[$this->intIndex]['content'] = $this->twig->createTemplate($content)->render($tagStorage->getAll()); 311 | } 312 | } 313 | 314 | return $this; 315 | } 316 | 317 | /** 318 | * Replace php tags and return content from partials. 319 | * 320 | * @throws \Exception 321 | */ 322 | public function getTagReplacedContentFromFilePath(string $strPath, TagStorage $tagStorage): string 323 | { 324 | $strPath = Path::canonicalize($strPath); 325 | 326 | $file = new \SplFileObject($strPath); 327 | 328 | $content = $this->fileGetContents($file); 329 | 330 | if ($this->isTemplate($file)) { 331 | return $this->twig->createTemplate($content)->render($tagStorage->getAll()); 332 | } 333 | 334 | return $content; 335 | } 336 | 337 | /** 338 | * Create the file in the target directory in vendor/vendorname/bundlename. 339 | */ 340 | public function createFile(string $targetPath): void 341 | { 342 | $targetPath = Path::canonicalize($targetPath); 343 | 344 | if (!$this->has($targetPath)) { 345 | throw new \Exception(\sprintf('File "%s" not found in the storage', $targetPath)); 346 | } 347 | 348 | $arrFile = $this->storage[$this->getIndexOf($targetPath)]; 349 | 350 | $parentDir = \dirname($arrFile['target']); 351 | 352 | $fs = new Filesystem(); 353 | 354 | if (!is_dir($parentDir)) { 355 | // Create directory recursive 356 | $fs->mkdir($parentDir); 357 | } 358 | 359 | $fs->dumpFile($arrFile['target'], $arrFile['content']); 360 | } 361 | 362 | private function getIndexOf(string $targetPath): int 363 | { 364 | foreach ($this->storage as $index => $arrFile) { 365 | if ($arrFile['target'] === $targetPath) { 366 | return $index; 367 | } 368 | } 369 | 370 | return -1; 371 | } 372 | 373 | /** 374 | * @throws \Exception 375 | */ 376 | private function sendFilePointerNotSetException() 377 | { 378 | return new \Exception('There is no pointer pointing to a file. Please use FileStorage::getFile() or FileStorage::addFile() or FileStorage::addFileFromString()'); 379 | } 380 | 381 | private function isTemplate(\SplFileObject $file): bool 382 | { 383 | if (!is_file($file->getRealPath())) { 384 | throw new FileNotFoundException(\sprintf('File "%s" not found.', $file->getRealPath())); 385 | } 386 | 387 | if ('ttpl' === $file->getExtension()) { 388 | return true; 389 | } 390 | 391 | return false; 392 | } 393 | 394 | private function fileGetContents(\SplFileObject $file): string 395 | { 396 | $content = ''; 397 | 398 | while (!$file->eof()) { 399 | $chunk = $file->fread(8192); // Read in 8KB chunks 400 | 401 | if (false === $chunk) { 402 | throw new \RuntimeException('Failed to read from file.'); 403 | } 404 | 405 | $content .= $chunk; 406 | } 407 | 408 | return $content; 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/BundleMaker/Str/Str.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\BundleMaker\Str; 16 | 17 | final class Str 18 | { 19 | /** 20 | * Sanitize vendor name (GitHub 6 packagist restrictions) 21 | * Do no allow: vendor_name, -vendorname, vendorname-, vendor--name, 22 | * But allow vendor-name. 23 | */ 24 | public static function asVendorName(string $value): string 25 | { 26 | $value = preg_replace('/_/', '-', $value); 27 | $value = preg_replace('/[\-]{2,}/', '-', $value); 28 | $value = preg_replace('/^-+|_+|[^A-Za-z0-9\-]|-+$/', '', $value); 29 | 30 | return strtolower($value); 31 | } 32 | 33 | /** 34 | * Sanitize repository name (GitHub restrictions) 35 | * Remove not accepted strings and replace them with "-" 36 | * contao-my-repository#" will be converted to "contao-my-repository-". 37 | */ 38 | public static function asRepositoryName(string $value, string $prefix = ''): string 39 | { 40 | $value = str_replace('#', '-', $value); 41 | $value = preg_replace('/[^A-Za-z0-9_\-]/', '-', $value); 42 | 43 | return self::addPrefix($value, $prefix); 44 | } 45 | 46 | /** 47 | * Ensures that the given string ends with the given prefix. If the string 48 | * already contains the prefix, it's not added twice. It's case-insensitive 49 | * (e.g. value: 'Foocommand' suffix: 'Command' -> result: 'FooCommand'). 50 | */ 51 | public static function addPrefix(string $value, string $prefix): string 52 | { 53 | return $prefix.self::removePrefix($value, $prefix); 54 | } 55 | 56 | /** 57 | * Ensures that the given string doesn't start with the given prefix. If the 58 | * string contains the prefix multiple times, only the first one is removed. 59 | * It's case-insensitive (e.g. value: 'Foocommand' suffix: 'Command' -> result: 'Foo'). 60 | */ 61 | public static function removePrefix(string $value, string $prefix): string 62 | { 63 | return self::hasPrefix($value, $prefix) ? substr($value, \strlen($prefix)) : $value; 64 | } 65 | 66 | /** 67 | * Looks for prefixes in strings in a case-insensitive way. 68 | */ 69 | public static function hasPrefix(string $value, string $prefix): bool 70 | { 71 | return 0 === stripos($value, $prefix); 72 | } 73 | 74 | /** 75 | * Sanitize composer description text 76 | * Replace double quotes with single quotes. 77 | */ 78 | public static function asComposerDescription(string $value): string 79 | { 80 | return str_replace('"', "'", $value); 81 | } 82 | 83 | /** 84 | * Get the backend module type (f.ex. my_custom_module) 85 | * Convention => snakecase. 86 | * 87 | * @param string $value (requires tl_contao_bundle_creator.backendmoduletype) 88 | */ 89 | public static function asContaoBackendModuleType(string $value): string 90 | { 91 | return self::asSnakeCase($value); 92 | } 93 | 94 | /** 95 | * Converts a string to snakecase 96 | * My custom module => my_custom_module. 97 | */ 98 | public static function asSnakeCase(string $value): string 99 | { 100 | $value = trim($value); 101 | $value = preg_replace('/[^a-zA-Z0-9_]/', '_', $value); 102 | $value = preg_replace('/(?<=\\w)([A-Z])/', '_$1', $value); 103 | $value = preg_replace('/_{2,}/', '_', $value); 104 | 105 | return strtolower($value); 106 | } 107 | 108 | /** 109 | * Return Dependeny Injection Extension Classname 110 | * e.g. ContaoCalendarExtension. 111 | */ 112 | public static function asDependencyInjectionExtensionClassname(string $vendorName, string $repositoryName): string 113 | { 114 | return preg_replace( 115 | '/Bundle$/', 116 | '', 117 | self::asClassName($vendorName).self::asClassName($repositoryName), 118 | ).'Extension'; 119 | } 120 | 121 | /** 122 | * Transforms the given string into the format commonly used by PHP classes, 123 | * (e.g. `this-app:do_this-and_that4you` -> `thisAppDoThisAndThat4You`), 124 | * but it doesn't check the validity of the class name. 125 | */ 126 | public static function asClassName(string $value, string $suffix = ''): string 127 | { 128 | $value = trim($value); 129 | $value = str_replace(['-', '_', '.', ':'], ' ', $value); 130 | $value = ucwords($value); 131 | $value = str_replace(' ', '', $value); 132 | $value = ucfirst($value); 133 | 134 | // Uppercase the first character that follows a number (positive lookbehind https://stackoverflow.com/questions/2341184/what-does-x-mean-in-regex) 135 | $value = preg_replace_callback('/(?<=\d)\w/', static fn ($matches) => strtoupper($matches[0]), $value); 136 | 137 | return self::addSuffix($value, $suffix); 138 | } 139 | 140 | /** 141 | * Ensures that the given string ends with the given suffix. If the string 142 | * already contains the suffix, it's not added twice. It's case-insensitive 143 | * (e.g. value: 'Foocommand' suffix: 'Command' -> result: 'FooCommand'). 144 | */ 145 | public static function addSuffix(string $value, string $suffix): string 146 | { 147 | return self::removeSuffix($value, $suffix).$suffix; 148 | } 149 | 150 | /** 151 | * Ensures that the given string doesn't end with the given suffix. If the 152 | * string contains the suffix multiple times, only the last one is removed. 153 | * It's case-insensitive (e.g. value: 'Foocommand' suffix: 'Command' -> result: 'Foo'). 154 | */ 155 | public static function removeSuffix(string $value, string $suffix): string 156 | { 157 | return self::hasSuffix($value, $suffix) ? substr($value, 0, -\strlen($suffix)) : $value; 158 | } 159 | 160 | /** 161 | * Looks for suffixes in strings in a case-insensitive way. 162 | */ 163 | public static function hasSuffix(string $value, string $suffix): bool 164 | { 165 | return 0 === strcasecmp($suffix, substr($value, -\strlen($suffix))); 166 | } 167 | 168 | /** 169 | * Get the frontend module classname from the module type and add the "Controller" suffix 170 | * f.ex. my_custom_module => MyCustomModuleController. 171 | * 172 | * @param string $value (requires tl_contao_bundle_creator.frontendmoduletype) 173 | */ 174 | public static function asContaoFrontendModuleClassName(string $value, string $suffix = 'Controller'): string 175 | { 176 | $value = self::asContaoFrontendModuleType($value); 177 | $value = self::asClassName($value); 178 | 179 | return self::addSuffix($value, $suffix); 180 | } 181 | 182 | /** 183 | * Get the frontend module type (f.ex. my_custom) 184 | * Convention => snakecase. 185 | * 186 | * @param string $value (requires tl_contao_bundle_creator.frontendmoduletype) 187 | * @param string $suffix (add a suffix e.g. "_module") 188 | */ 189 | public static function asContaoFrontendModuleType(string $value, string $suffix = ''): string 190 | { 191 | $value = self::asSnakeCase($value); 192 | 193 | $pattern = '/^(module_|module|mod_|mod|_{1})/'; 194 | $value = preg_replace($pattern, '', $value); 195 | 196 | $pattern = '/_{1}$/'; 197 | $value = preg_replace($pattern, '', $value); 198 | 199 | // Add suffix 200 | return self::addSuffix($value, $suffix); 201 | } 202 | 203 | /** 204 | * Get the content element classname from element type and add the "Controller" suffix 205 | * f.ex. my_custom_element => MyCustomElementController. 206 | * 207 | * @param string $value (requires tl_contao_bundle_creator.contentelementtype) 208 | */ 209 | public static function asContaoContentElementClassName(string $value, string $suffix = 'Controller'): string 210 | { 211 | $value = self::asContaoContentElementType($value); 212 | $value = self::asClassName($value); 213 | 214 | return self::addSuffix($value, $suffix); 215 | } 216 | 217 | /** 218 | * Get the content element type (f.ex. my_custom) 219 | * Convention => snakecase. 220 | * 221 | * @param string $value (requires tl_contao_bundle_creator.contentelementtype) 222 | * @param string $suffix (add a suffix e.g. "_element") 223 | */ 224 | public static function asContaoContentElementType(string $value, string $suffix = ''): string 225 | { 226 | $value = self::asSnakeCase($value); 227 | 228 | $pattern = '/^(element_|element|ce_|ce|_{1})/'; 229 | $value = preg_replace($pattern, '', $value); 230 | 231 | $pattern = '/_{1}$/'; 232 | $value = preg_replace($pattern, '', $value); 233 | 234 | // Add suffix 235 | return self::addSuffix($value, $suffix); 236 | } 237 | 238 | /** 239 | * Get model classname f.ex. SampleTable. 240 | * 241 | * @param string $value (requires tl_contao_bundle_creator.dcatable) 242 | * 243 | * @throws \Exception 244 | */ 245 | public static function asContaoModelClassName(string $value, string $suffix = 'Model'): string 246 | { 247 | $value = self::asContaoDcaTable($value); 248 | $value = preg_replace('/^tl_/', '', $value); 249 | 250 | return self::asClassName($value, $suffix); 251 | } 252 | 253 | /** 254 | * Get the sanitized dca tablename f.ex. tl_sample_table. 255 | * 256 | * @param string $value (requires tl_contao_bundle_creator.dcatable) 257 | * 258 | * @throws \Exception 259 | */ 260 | public static function asContaoDcaTable(string $value): string 261 | { 262 | if (!\strlen($value)) { 263 | throw new \Exception('No dca tablename set.'); 264 | } 265 | 266 | $value = strtolower($value); 267 | $value = preg_replace('/-|\s/', '_', $value); 268 | $value = preg_replace('/_{2,}/', '_', $value); 269 | $value = preg_replace('/[^A-Za-z0-9_]|_$/', '', $value); 270 | 271 | if (!str_starts_with($value, 'tl_')) { 272 | $value = 'tl_'.$value; 273 | } 274 | 275 | return $value; 276 | } 277 | 278 | public static function asContaoFrontendModuleTemplateName(string $value, $prefix = ''): string 279 | { 280 | $value = self::asContaoFrontendModuleType($value); 281 | $value = self::addPrefix($value, $prefix); 282 | 283 | return preg_replace('/_{2,}/', '_', $value); 284 | } 285 | 286 | public static function asContaoContentElementTemplateName(string $value, $prefix = ''): string 287 | { 288 | $value = self::asContaoContentElementType($value); 289 | $value = self::addPrefix($value, $prefix); 290 | 291 | return preg_replace('/_{2,}/', '_', $value); 292 | } 293 | 294 | /** 295 | * Returns the dca class name: e.g. "tl_pet_cat" => "PetCat". 296 | */ 297 | public static function asDcaClassName(string $dcaTableName): string 298 | { 299 | return preg_replace( 300 | '/^Tl/', 301 | '', 302 | self::asClassName($dcaTableName), 303 | ); 304 | } 305 | 306 | /** 307 | * Returns the twig namespace: e.g. @MarkocupicContaoBundleCreator. 308 | */ 309 | public static function asTwigNamespace(string $vendorName, string $repositoryName): string 310 | { 311 | return preg_replace( 312 | '/Bundle$/', 313 | '', 314 | '@'.self::asClassName($vendorName).self::asClassName($repositoryName), 315 | ); 316 | } 317 | 318 | /** 319 | * Generate phpdoc header comment from string. 320 | */ 321 | public static function generatePhpDocStringForECS(string $value): string 322 | { 323 | // Left and right trim 324 | $value = str_replace(['/*', ' */', ' * ', ' *'], ['', '', '', '', ''], $value); 325 | 326 | // Trim empty lines 327 | $value = preg_replace('/^\s+|\s+$/u', '', $value); 328 | 329 | $lines = explode("\n", $value); 330 | 331 | return implode('\n', $lines); 332 | } 333 | 334 | /** 335 | * Converts string into session attribute name f.eg markocupic_my_bundle_attribute. 336 | */ 337 | public static function asSessionAttributeName(string $value): string 338 | { 339 | $value = strtolower($value); 340 | $value = preg_replace('/[^a-z0-9]/i', '_', $value); 341 | $value = preg_replace('/_{2,}/', '_', $value); 342 | $value = preg_replace('/^_{1}/', '', $value); 343 | 344 | return preg_replace('/_{1}$/', '', $value); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | * For the full copyright and license information, 11 | * please view the LICENSE file that was distributed with this source code. 12 | * @link https://github.com/markocupic/contao-bundle-creator-bundle 13 | */ 14 | 15 | namespace Markocupic\ContaoBundleCreatorBundle\DependencyInjection; 16 | 17 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 18 | use Symfony\Component\Config\Definition\ConfigurationInterface; 19 | use Symfony\Component\Yaml\Yaml; 20 | 21 | class Configuration implements ConfigurationInterface 22 | { 23 | public const ROOT_KEY = 'markocupic_contao_bundle_creator'; 24 | 25 | public function __construct(private readonly string $projectDir) 26 | { 27 | } 28 | 29 | public function getConfigTreeBuilder(): TreeBuilder 30 | { 31 | $treeBuilder = new TreeBuilder(self::ROOT_KEY); 32 | 33 | $treeBuilder->getRootNode() 34 | ->children() 35 | // Language 36 | ->scalarNode('defaultSkeletonPath')->info('Set the default language.')->defaultValue('en')->cannotBeEmpty()->end() 37 | // Section name 38 | ->scalarNode('section_name')->info('e.g. SAC Sektion Pilatus')->cannotBeEmpty()->end() 39 | // Member database sync Zentralverband Bern 40 | ->arrayNode('member_sync_credentials') 41 | ->children() 42 | ->scalarNode('hostname')->cannotBeEmpty()->end() 43 | ->scalarNode('username')->cannotBeEmpty()->end() 44 | ->scalarNode('password')->cannotBeEmpty()->end() 45 | ->end() 46 | ->end() 47 | // Event admin name 48 | ->scalarNode('event_admin_name')->cannotBeEmpty()->end() 49 | // Event admin email 50 | ->scalarNode('event_admin_email')->cannotBeEmpty()->end() 51 | // Temp dir e.g system/tmp 52 | ->scalarNode('temp_dir')->defaultValue('system/tmp')->cannotBeEmpty()->end() 53 | // Avatars 54 | ->arrayNode('avatar') 55 | ->addDefaultsIfNotSet() 56 | ->children() 57 | ->scalarNode('female')->defaultValue('vendor/markocupic/sac-event-tool-bundle/public/images/avatars/avatar-default-female.png')->end() 58 | ->scalarNode('male')->defaultValue('vendor/markocupic/sac-event-tool-bundle/public/images/avatars/avatar-default-male.png')->end() 59 | ->scalarNode('other')->defaultValue('vendor/markocupic/sac-event-tool-bundle/public/images/avatars/avatar-default-other.png')->end() 60 | ->end() 61 | ->end() 62 | // Backend and frontend users 63 | ->arrayNode('user') 64 | ->addDefaultsIfNotSet() 65 | ->children() 66 | ->arrayNode('backend') 67 | ->addDefaultsIfNotSet() 68 | ->children() 69 | ->scalarNode('home_dir')->defaultValue('files/sektion/be_user_home_directories')->end() 70 | ->booleanNode('reset_permissions_on_login')->defaultFalse()->end() 71 | ->arrayNode('rescission_cause') 72 | ->prototype('scalar')->end() 73 | ->defaultValue(['accident', 'deceased', 'recission', 'leaving', 'pausing', 'another']) 74 | ->end() 75 | ->end() 76 | ->end() 77 | ->arrayNode('frontend') 78 | ->addDefaultsIfNotSet() 79 | ->children() 80 | ->scalarNode('home_dir')->defaultValue('files/sektion/fe_user_home_directories')->end() 81 | ->scalarNode('avatar_dir')->defaultValue('files/sektion/fe_user_home_directories/avatars')->end() 82 | ->end() 83 | ->end() 84 | ->end() 85 | ->end() 86 | // Events 87 | ->arrayNode('event') 88 | ->addDefaultsIfNotSet() 89 | ->children() 90 | ->arrayNode('config') 91 | ->addDefaultsIfNotSet() 92 | ->children() 93 | ->arrayNode('duration_info') 94 | ->useAttributeAsKey('name') 95 | ->prototype('scalar')->end() 96 | ->normalizeKeys(false) 97 | ->defaultValue(Yaml::parse(file_get_contents($this->projectDir.'/vendor/markocupic/sac-event-tool-bundle/config/defaults/event_duration_opt.yaml'))) 98 | ->end() 99 | ->arrayNode('avalanche_level') 100 | ->prototype('scalar')->end() 101 | ->defaultValue(['avalanche_level_0', 'avalanche_level_1', 'avalanche_level_2', 'avalanche_level_3', 'avalanche_level_4', 'avalanche_level_5']) 102 | ->end() 103 | ->end() 104 | ->end() 105 | ->arrayNode('course') 106 | ->addDefaultsIfNotSet() 107 | ->children() 108 | ->arrayNode('levels') 109 | ->useAttributeAsKey('name') 110 | ->prototype('scalar')->end() 111 | ->normalizeKeys(false) 112 | ->defaultValue(Yaml::parse(file_get_contents($this->projectDir.'/vendor/markocupic/sac-event-tool-bundle/config/defaults/course_level_opt.yaml'))) 113 | ->end() 114 | ->scalarNode('booklet_cover_image')->defaultValue('vendor/markocupic/sac-event-tool-bundle/public/images/events/course/booklet/cover.jpg')->end() 115 | ->scalarNode('booklet_filename_pattern')->defaultValue('Kursprogramm_%%s.pdf')->end() 116 | ->scalarNode('fallback_image')->defaultValue('vendor/markocupic/sac-event-tool-bundle/public/images/events/course/fallback_image.svg')->end() 117 | ->end() 118 | ->end() 119 | ->arrayNode('template') 120 | ->addDefaultsIfNotSet() 121 | ->children() 122 | // Event member list docx template 123 | ->scalarNode('member_list')->defaultValue('vendor/markocupic/sac-event-tool-bundle/contao/templates/docx/event_memberlist.docx')->end() 124 | // Event tour invoice docx template 125 | ->scalarNode('tour_invoice')->defaultValue('vendor/markocupic/sac-event-tool-bundle/contao/templates/docx/event_invoice_tour.docx')->end() 126 | // Event tour rapport docx template 127 | ->scalarNode('tour_rapport')->defaultValue('vendor/markocupic/sac-event-tool-bundle/contao/templates/docx/event_rapport_tour.docx')->end() 128 | // Event course confirmation docx template 129 | ->scalarNode('course_confirmation')->defaultValue('vendor/markocupic/sac-event-tool-bundle/contao/templates/docx/course_confirmation.docx')->end() 130 | ->end() 131 | ->end() 132 | // Member list file name pattern 133 | ->scalarNode('member_list_file_name_pattern')->defaultValue('SAC_Event_Teilnehmerliste_%%s.%%s')->end() 134 | // Event tour invoice file name pattern 135 | ->scalarNode('tour_invoice_file_name_pattern')->defaultValue('SAC_Event_Verguetungsformular_%%s.%%s')->end() 136 | // Event tour rapport file name pattern 137 | ->scalarNode('tour_rapport_file_name_pattern')->defaultValue('SAC_Event_Tourrapport_%%s.%%s')->end() 138 | // Event course confirmation file name pattern 139 | ->scalarNode('course_confirmation_file_name_pattern')->defaultValue('SAC_Event_Kursbestaetigung_%%s_regId_%%s.%%s')->end() 140 | // Coordinates 141 | ->scalarNode('geo_link') 142 | ->cannotBeEmpty() 143 | // The coord "%s" placeholders have to be escaped by an additional percent char 144 | // => %%s 145 | ->defaultValue('https://map.geo.admin.ch/embed.html?lang=de&topic=ech&bgLayer=ch.swisstopo.pixelkarte-farbe&layers=ch.bav.haltestellen-oev,ch.swisstopo.swisstlm3d-wanderwege,ch.swisstopo-karto.skitouren,ch.astra.wanderland-sperrungen_umleitungen&E=%%s&N=%%s&zoom=6&crosshair=marker') 146 | ->end() 147 | // SAC Route Portal Base Link 148 | ->scalarNode('sac_route_portal_base_link') 149 | ->cannotBeEmpty() 150 | ->defaultValue('https://www.sac-cas.ch/de/huetten-und-touren/sac-tourenportal/') 151 | ->end() 152 | ->end() 153 | ->end() 154 | ->arrayNode('event_registration') 155 | ->addDefaultsIfNotSet() 156 | ->children() 157 | ->arrayNode('config') 158 | ->addDefaultsIfNotSet() 159 | ->children() 160 | ->scalarNode('email_accept_templ_path') 161 | ->defaultValue($this->projectDir.'/vendor/markocupic/sac-event-tool-bundle/templates/Email/EventRegistration/email_reg_accept.twig') 162 | ->end() 163 | ->scalarNode('email_cancel_templ_path') 164 | ->defaultValue($this->projectDir.'/vendor/markocupic/sac-event-tool-bundle/templates/Email/EventRegistration/email_reg_cancel.twig') 165 | ->end() 166 | ->scalarNode('email_refuse_templ_path') 167 | ->defaultValue($this->projectDir.'/vendor/markocupic/sac-event-tool-bundle/templates/Email/EventRegistration/email_reg_refuse.twig') 168 | ->end() 169 | ->scalarNode('email_waitinglist_templ_path') 170 | ->defaultValue($this->projectDir.'/vendor/markocupic/sac-event-tool-bundle/templates/Email/EventRegistration/email_reg_waitinglist.twig') 171 | ->end() 172 | // Custom email text for accepting registrations 173 | ->scalarNode('email_accept_custom_templ_path') 174 | ->cannotBeEmpty() 175 | ->info('The instructor can send a customized email for accepting registrations in the Contao backend.') 176 | ->defaultValue($this->projectDir.'/vendor/markocupic/sac-event-tool-bundle/templates/Email/EventRegistration/email_reg_accept_personalized.txt') 177 | ->end() 178 | ->arrayNode('car_seat_info') 179 | ->prototype('scalar')->end() 180 | ->defaultValue(['kein Auto', '2', '3', '4', '5', '6', '7', '8', '9']) 181 | ->end() 182 | ->arrayNode('ticket_info') 183 | ->prototype('scalar')->end() 184 | ->defaultValue(['Nichts', 'GA', 'Halbtax-Abo']) 185 | ->end() 186 | ->integerNode('reg_start_time_offset') 187 | ->info('Number of seconds to be added to tl_calendar_events-registrationStartTime to calculate the exact time from which registrations should be possible.') 188 | ->defaultValue(6 * 60 * 60) // 6h 189 | ->end() 190 | ->end() 191 | ->end() 192 | ->end() 193 | ->end() 194 | ->end() 195 | ; 196 | 197 | return $treeBuilder; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /contao/languages/en/tl_contao_bundle_creator.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Edit ID %s 7 | 8 | 9 | Edit bundle ID %s 10 | 11 | 12 | Duplicate bundle ID %s 13 | 14 | 15 | Duplicate bundle ID %s 16 | 17 | 18 | Delete bundle ID %s 19 | 20 | 21 | Delete bundle ID %s 22 | 23 | 24 | Watch details bundle ID %s 25 | 26 | 27 | Watch details bundle ID %s 28 | 29 | 30 | Bundle settings 31 | 32 | 33 | composer.json settings 34 | 35 | 36 | (ROOT-)composer.json settings 37 | 38 | 39 | DCA table settings 40 | 41 | 42 | Frontend element settings 43 | 44 | 45 | Content element settings 46 | 47 | 48 | Custom route settings 49 | 50 | 51 | Session attribute settings 52 | 53 | 54 | Friendly configuration settings 55 | 56 | 57 | Coding Style Tools 58 | 59 | 60 | Bundle name 61 | 62 | 63 | Please enter a name for the bundle. 64 | 65 | 66 | Vendor name 67 | 68 | 69 | Please enter a vendor name. 70 | 71 | 72 | (Github-) repository name (use "contao-" as prefix) 73 | 74 | 75 | Please enter the repository name. 76 | 77 | 78 | Override bundle of the same name 79 | 80 | 81 | Should the bundle of the same name be overwritten? 82 | 83 | 84 | composer.json: description text 85 | 86 | 87 | Please enter the text for the description section in the composer.json file of your new bundle. 88 | 89 | 90 | composer.json: package version (Mandatory for package uploads with the Contao Manager) 91 | 92 | 93 | Please enter the package version in format 1.x (Mandatory only for package uploads with the Contao Manager). 94 | 95 | 96 | Edit composer.json (ROOT) 97 | 98 | 99 | Authorize the bundle generator editing your composer.json (ROOT)? 100 | 101 | 102 | composer.json (ROOT): Extend the file with an entry at the key: "repositories"? 103 | 104 | 105 | Should your (ROOT-) composer.json be extended with an entry at the key: "repositories"? 106 | 107 | 108 | composer.json: license 109 | 110 | 111 | Please enter the license type. e.g. MIT. 112 | 113 | 114 | composer.json: author name 115 | 116 | 117 | Please enter the author's name. 118 | 119 | 120 | composer.json: author email address 121 | 122 | 123 | Please enter the authors's email address. 124 | 125 | 126 | composer.json: website 127 | 128 | 129 | Please enter the author's website. e.g. https://github.com/vendorname 130 | 131 | 132 | Add a backend module with a dca table 133 | 134 | 135 | Should a backend module with a dca table be added? 136 | 137 | 138 | Backend module category 139 | 140 | 141 | Please enter the backend module category as a snakecased string. 142 | 143 | 144 | Backend module category translation 145 | 146 | 147 | Let the field empty if the category and the translation already exists. 148 | 149 | 150 | Backendmodule type 151 | 152 | 153 | Please enter the backend module type as a snakecased string. 154 | 155 | 156 | Backend module type translation and description 157 | 158 | 159 | Please add a name and a description for the backend module. 160 | 161 | 162 | DCA table name 163 | 164 | 165 | Please enter a name for the table: e.g. tl_my_pets 166 | 167 | 168 | Add a frontend module 169 | 170 | 171 | Add a frontend module to the bundle. 172 | 173 | 174 | Frontend module category (snakecase) 175 | 176 | 177 | Please enter the frontend module category as a snakecased string. 178 | 179 | 180 | Frontend module category translation 181 | 182 | 183 | Let the field empty if the category and the translation already exists. 184 | 185 | 186 | Frontend module type (snakecase) 187 | 188 | 189 | Please enter the frontend module type as a snakecased string. 190 | 191 | 192 | Frontend module type name and description 193 | 194 | 195 | Please add a name and a description for the frontend module. 196 | 197 | 198 | Add a content element 199 | 200 | 201 | Add a content element to the bundle. 202 | 203 | 204 | Content element category (snakecase) 205 | 206 | 207 | Please enter the content element category as a snakecased string. 208 | 209 | 210 | Content element category translation 211 | 212 | 213 | Let the field empty if the category and the translation already exists. 214 | 215 | 216 | Content element type (snakecase) 217 | 218 | 219 | Please enter the content element type as a snakecased string. 220 | 221 | 222 | Content element type name and description 223 | 224 | 225 | Please add a name and a description for the content element. 226 | 227 | 228 | Add a custom route 229 | 230 | 231 | Should a custom route be added to the bundle? 232 | 233 | 234 | Add easy-coding-standard configuration 235 | 236 | 237 | Add easy-coding-standard configuration. 238 | 239 | 240 | Generate bundle 241 | 242 | 243 | Download bundle 244 | 245 | 246 | Add extra session attribute (session bag) 247 | 248 | 249 | Add an extra session attribute (session bag). 250 | 251 | 252 | Add friendly configuration 253 | 254 | 255 | Add friendly configuration. 256 | 257 | 258 | 259 | 260 | --------------------------------------------------------------------------------