├── 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 | 
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 |
11 | - {{ animal.color }} {{ animal.species }}
12 |
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 | 
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 | 
25 |
26 | ## Verzeichnisstruktur
27 | Folgende Verzeichnisstruktur (ohne Resources Verzeichnis) wird im vendor Ordner angelegt.
28 |
29 | 
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 | 
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 |
--------------------------------------------------------------------------------