├── LICENSE
├── README.md
├── composer.json
├── config
├── callbacks.yaml
├── events.yaml
├── hooks.yaml
└── services.yaml
├── skeleton
├── content-element
│ ├── ContentElement.tpl.php
│ ├── content_element.tpl.html.twig
│ └── tl_content.tpl.php
├── dca-callback
│ └── Callback.tpl.php
├── event-listener
│ └── EventListener.tpl.php
├── frontend-module
│ ├── FrontendModule.tpl.php
│ ├── frontend_module.tpl.html.twig
│ └── tl_module.tpl.php
└── hook
│ └── Hook.tpl.php
└── src
├── ContaoMakerBundle.php
├── ContaoManager
└── Plugin.php
├── DependencyInjection
└── ContaoMakerExtension.php
├── Generator
├── ClassGenerator.php
├── DcaGenerator.php
├── GeneratorInterface.php
├── LanguageFileGenerator.php
└── TemplateGenerator.php
├── Maker
├── AbstractFragmentMaker.php
├── MakeContentElement.php
├── MakeDcaCallback.php
├── MakeEventListener.php
├── MakeFrontendModule.php
└── MakeHook.php
└── Reflection
├── ImportExtractor.php
├── MethodDefinition.php
└── SignatureGenerator.php
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Contao maker bundle
2 |
3 | [](https://packagist.org/packages/contao/maker-bundle)
4 | [](https://packagist.org/packages/contao/maker-bundle)
5 |
6 | The maker bundle allows you to generate content elements, front end modules, event listener, callbacks and hooks using
7 | interactive commands.
8 |
9 | Contao is an Open Source PHP Content Management System for people who want a professional website that is easy to
10 | maintain. Visit the [project website][1] for more information.
11 |
12 | ## Installation
13 |
14 | Run this command to install and enable the bundle in your application:
15 |
16 | ```
17 | composer require contao/maker-bundle --dev
18 | ```
19 |
20 | ## Usage
21 |
22 | This bundle provides several commands under the `make:` namespace. You can list them all with the following command:
23 |
24 | ```
25 | php bin/console list make:contao
26 |
27 | Available commands for the "make:contao" namespace:
28 | make:contao:content-element Creates a new content element
29 | make:contao:dca-callback Creates a new DCA callback listener
30 | make:contao:event-listener Creates a new event listener
31 | make:contao:frontend-module Creates a new front end module
32 | make:contao:hook Creates a new hook listener
33 | ```
34 |
35 | ## License
36 |
37 | Contao is licensed under the terms of the LGPLv3.
38 |
39 | ## Getting support
40 |
41 | Visit the [support page][2] to learn about the available support options.
42 |
43 | [1]: https://contao.org
44 | [2]: https://to.contao.org/support
45 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "contao/maker-bundle",
3 | "description": "Allows you to create content elements, modules, hooks and more",
4 | "license": "LGPL-3.0-or-later",
5 | "type": "contao-bundle",
6 | "authors": [
7 | {
8 | "name": "Leo Feyer",
9 | "homepage": "https://github.com/leofeyer"
10 | },
11 | {
12 | "name": "Contao Community",
13 | "homepage": "https://contao.org/contributors"
14 | }
15 | ],
16 | "homepage": "https://contao.org",
17 | "support": {
18 | "issues": "https://github.com/contao/contao/issues",
19 | "forum": "https://community.contao.org",
20 | "source": "https://github.com/contao/maker-bundle",
21 | "docs": "https://docs.contao.org"
22 | },
23 | "funding": [
24 | {
25 | "type": "other",
26 | "url": "https://to.contao.org/donate"
27 | }
28 | ],
29 | "require": {
30 | "php": "^8.2",
31 | "contao/core-bundle": "self.version",
32 | "symfony/filesystem": "^6.4 || ^7.0",
33 | "symfony/maker-bundle": "^1.1",
34 | "symfony/options-resolver": "^6.4 || ^7.0",
35 | "symfony/yaml": "^6.4 || ^7.0"
36 | },
37 | "require-dev": {
38 | "contao/manager-plugin": "^2.3.1",
39 | "phpunit/phpunit": "^11.5"
40 | },
41 | "conflict": {
42 | "contao/core": "*",
43 | "contao/manager-plugin": "<2.0 || >=3.0"
44 | },
45 | "autoload": {
46 | "psr-4": {
47 | "Contao\\MakerBundle\\": "src/"
48 | }
49 | },
50 | "autoload-dev": {
51 | "psr-4": {
52 | "Contao\\MakerBundle\\Fixtures\\": "tests/Fixtures/src/",
53 | "Contao\\MakerBundle\\Tests\\": "tests/"
54 | }
55 | },
56 | "config": {
57 | "allow-plugins": {
58 | "contao-components/installer": true,
59 | "contao/manager-plugin": true,
60 | "php-http/discovery": false
61 | }
62 | },
63 | "extra": {
64 | "contao-manager-plugin": "Contao\\MakerBundle\\ContaoManager\\Plugin"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/config/callbacks.yaml:
--------------------------------------------------------------------------------
1 | callbacks:
2 | config.onload:
3 | return_type: void
4 | arguments:
5 | dataContainer: Contao\DataContainer
6 |
7 | config.oncreate:
8 | return_type: void
9 | arguments:
10 | table: string
11 | insertId: int
12 | fields: array
13 | dataContainer: Contao\DataContainer
14 |
15 | config.onsubmit:
16 | return_type: void
17 | # Since there are multiple parameters for multiple calls, we cannot
18 | # safely assume the correct parameter names and types
19 | arguments: []
20 |
21 | config.ondelete:
22 | return_type: void
23 | arguments:
24 | dataContainer: Contao\DataContainer
25 | id: int
26 |
27 | config.oncut:
28 | return_type: void
29 | arguments:
30 | dataContainer: Contao\DataContainer
31 |
32 | config.oncopy:
33 | return_type: void
34 | arguments:
35 | id: int
36 | dataContainer: Contao\DataContainer
37 |
38 | config.oncreate_version:
39 | return_type: void
40 | arguments:
41 | table: string
42 | pid: int
43 | versionNumber: int
44 | recordData: array
45 |
46 | config.onrestore_version:
47 | return_type: void
48 | arguments:
49 | table: string
50 | pid: int
51 | versionNumber: int
52 | recordData: array
53 |
54 | config.onundo:
55 | return_type: void
56 | arguments:
57 | table: string
58 | recordData: array
59 | dataContainer: Contao\DataContainer
60 |
61 | config.oninvalidate_cache_tags:
62 | return_type: array
63 | arguments:
64 | dataContainer: Contao\DataContainer
65 | tags: array
66 |
67 | config.onshow:
68 | return_type: array
69 | arguments:
70 | modalData: array
71 | recordData: array
72 | dataContainer: Contao\DataContainer
73 |
74 | list.sorting.paste_button:
75 | return_type: string
76 | arguments:
77 | dataContainer: Contao\DataContainer
78 | recordData: array
79 | table: string
80 | isCircularReference: bool
81 | clipboardData: array
82 | children: array
83 | previousLabel: string
84 | nextLabel: string
85 |
86 | list.sorting.child_record:
87 | return_type: string
88 | arguments:
89 | recordData: array
90 |
91 | list.sorting.header:
92 | return_type: array
93 | arguments:
94 | currentHeaderLabels: array
95 | dataContainer: Contao\DataContainer
96 |
97 | list.sorting.panel_callback.subpanel:
98 | return_type: string
99 | arguments:
100 | dataContainer: Contao\DataContainer
101 |
102 | list.label.group:
103 | return_type: string
104 | arguments:
105 | group: string
106 | mode: string
107 | field: string
108 | recordData: array
109 | dataContainer: Contao\DataContainer
110 |
111 | list.label.label:
112 | return_type: array
113 | arguments:
114 | recordData: array
115 | currentLabel: string
116 | dataContainer: Contao\DataContainer
117 | # Since there are multiple parameters for multiple calls, we cannot
118 | # safely assume the following correct parameter names and types
119 |
120 | list.global_operations.{operation}.button:
121 | return_type: string
122 | arguments:
123 | buttonHref: ?string
124 | label: string
125 | title: string
126 | className: string
127 | htmlAttributes: string
128 | table: string
129 | rootRecordIds: array
130 |
131 | list.operations.{operation}.button:
132 | return_type: string
133 | arguments:
134 | recordData: array
135 | buttonHref: ?string
136 | label: string
137 | title: string
138 | icon: ?string
139 | htmlAttributes: string
140 | table: string
141 | rootRecordIds: array
142 | childRecordIds: array
143 | isCircularReference: bool
144 | previousLabel: string
145 | nextLabel: string
146 | dataContainer: Contao\DataContainer
147 |
148 | fields.{field}.options:
149 | return_type: array
150 | arguments:
151 | dataContainer: Contao\DataContainer
152 |
153 | fields.{field}.attributes:
154 | return_type: array
155 | arguments:
156 | attributes: array
157 | dataContainer: Contao\DataContainer
158 | body: >
159 | // Do something
160 | return $attributes;
161 |
162 | fields.{field}.input_field:
163 | return_type: string
164 | arguments:
165 | dataContainer: Contao\DataContainer
166 |
167 | fields.{field}.load:
168 | return_type: ~
169 | arguments:
170 | value: ~
171 | # Since there are multiple parameters for multiple calls, we cannot
172 | # safely assume the following correct parameter names and types
173 | body: >
174 | return $value;
175 |
176 | fields.{field}.save:
177 | return_type: ~
178 | arguments:
179 | value: ~
180 | # Since there are multiple parameters for multiple calls, we cannot
181 | # safely assume the following correct parameter names and types
182 | body: >
183 | return $value;
184 |
185 | fields.{field}.wizard:
186 | return_type: string
187 | arguments:
188 | dataContainer: Contao\DataContainer
189 |
190 | fields.{field}.xlabel:
191 | return_type: string
192 | arguments:
193 | dataContainer: Contao\DataContainer
194 |
--------------------------------------------------------------------------------
/config/events.yaml:
--------------------------------------------------------------------------------
1 | events:
2 | contao.backend_menu_build:
3 | return_type: void
4 | arguments:
5 | event: Contao\CoreBundle\Event\MenuEvent
6 |
7 | contao.generate_symlinks:
8 | return_type: void
9 | arguments:
10 | event: Contao\CoreBundle\Event\GenerateSymlinksEvent
11 |
12 | contao.image_sizes_all:
13 | return_type: void
14 | arguments:
15 | event: Contao\CoreBundle\Event\ImageSizesEvent
16 |
17 | contao.image_sizes_user:
18 | return_type: void
19 | arguments:
20 | event: Contao\CoreBundle\Event\ImageSizesEvent
21 |
22 | contao.preview_url_create:
23 | return_type: void
24 | arguments:
25 | event: Contao\CoreBundle\Event\PreviewUrlCreateEvent
26 |
27 | contao.preview_url_convert:
28 | return_type: void
29 | arguments:
30 | event: Contao\CoreBundle\Event\PreviewUrlConvertEvent
31 |
32 | contao.robots_txt:
33 | return_type: void
34 | arguments:
35 | event: Contao\CoreBundle\Event\RobotsTxtEvent
36 |
37 | contao.slug_valid_characters:
38 | return_type: void
39 | arguments:
40 | event: Contao\CoreBundle\Event\SlugValidCharactersEvent
41 |
42 | Contao\CoreBundle\Event\FilterPageTypeEvent:
43 | return_type: void
44 | arguments:
45 | event: Contao\CoreBundle\Event\FilterPageTypeEvent
46 |
--------------------------------------------------------------------------------
/config/hooks.yaml:
--------------------------------------------------------------------------------
1 | hooks:
2 | activateAccount:
3 | return_type: void
4 | arguments:
5 | member: Contao\MemberModel
6 | module: Contao\Module
7 |
8 | activateRecipient:
9 | return_type: void
10 | arguments:
11 | mail: string
12 | recipientIds: array
13 | channelIds: array
14 |
15 | addComment:
16 | return_type: void
17 | arguments:
18 | commentId: int
19 | commentData: array
20 | comments: Contao\Comments
21 |
22 | addCustomRegexp:
23 | return_type: bool
24 | arguments:
25 | regexp: string
26 | input: ''
27 | widget: Contao\Widget
28 |
29 | closeAccount:
30 | return_type: void
31 | arguments:
32 | userId: int
33 | mode: string
34 | module: Contao\Module
35 |
36 | colorizeLogEntries:
37 | return_type: string
38 | arguments:
39 | row: array
40 | label: string
41 |
42 | compareThemeFiles:
43 | return_type: string
44 | arguments:
45 | xml: DomDocument
46 | zip: Contao\ZipReader
47 |
48 | compileArticle:
49 | return_type: void
50 | arguments:
51 | template: Contao\FrontendTemplate
52 | data: array
53 | module: Contao\Module
54 |
55 | compileDefinition:
56 | return_type: string
57 | arguments:
58 | row: array
59 | writeToFile: bool
60 | vars: array
61 | parent: array
62 |
63 | compileFormFields:
64 | return_type: array
65 | arguments:
66 | fields: array
67 | formId: string
68 | form: Contao\Form
69 |
70 | createDefinition:
71 | return_type: array
72 | arguments:
73 | key: string
74 | value: string
75 | definition: string
76 | '&dataSet': array
77 |
78 | createNewUser:
79 | return_type: void
80 | arguments:
81 | userId: int
82 | userData: array
83 | module: Contao\Module
84 |
85 | customizeSearch:
86 | return_type: void
87 | arguments:
88 | '&pageIds': array
89 | keywords: string
90 | queryType: string
91 | fuzzy: bool
92 | module: Contao\Module
93 |
94 | executePostActions:
95 | return_type: void
96 | arguments:
97 | action: string
98 | dc: Contao\DataContainer
99 |
100 | executePreActions:
101 | return_type: void
102 | arguments:
103 | action: string
104 |
105 | executeResize:
106 | return_type: ?string
107 | arguments:
108 | image: Contao\Image
109 |
110 | exportTheme:
111 | return_type: void
112 | arguments:
113 | xml: DomDocument
114 | zipArchive: Contao\ZipWriter
115 | themeId: int
116 |
117 | extractThemeFiles:
118 | return_type: void
119 | arguments:
120 | xml: DomDocument
121 | zipArchive: Contao\ZipReader
122 | themeId: int
123 | mapper: array
124 |
125 | generateBreadcrumb:
126 | return_type: array
127 | arguments:
128 | items: array
129 | module: Contao\Module
130 |
131 | generateFrontendUrl:
132 | return_type: string
133 | arguments:
134 | page: array
135 | params: string
136 | url: string
137 |
138 | generatePage:
139 | return_type: void
140 | arguments:
141 | pageModel: Contao\PageModel
142 | layout: Contao\LayoutModel
143 | pageRegular: Contao\PageRegular
144 |
145 | generateXmlFiles:
146 | return_type: void
147 | arguments: []
148 |
149 | getAllEvents:
150 | return_type: array
151 | arguments:
152 | events: array
153 | calendars: array
154 | timeStart: int
155 | timeEnd: int
156 | module: Contao\Module
157 |
158 | getArticle:
159 | return_type: void
160 | arguments:
161 | article: Contao\ArticleModel
162 |
163 | getArticles:
164 | return_type: ?string
165 | arguments:
166 | pageId: int
167 | column: string
168 |
169 | getAttributesFromDca:
170 | return_type: array
171 | arguments:
172 | attributes: array
173 | dc: [Contao\DataContainer, 'null']
174 |
175 | getCombinedFile:
176 | return_type: string
177 | arguments:
178 | content: string
179 | key: string
180 | mode: string
181 | file: array
182 |
183 | getContentElement:
184 | return_type: string
185 | arguments:
186 | contentModel: Contao\ContentModel
187 | buffer: string
188 | contentElement: Contao\ContentElement
189 |
190 | getCountries:
191 | return_type: void
192 | arguments:
193 | '&translatedCountries': array
194 | allCountries: array
195 |
196 | getForm:
197 | return_type: string
198 | arguments:
199 | form: Contao\FormModel
200 | buffer: string
201 |
202 | getFrontendModule:
203 | return_type: string
204 | arguments:
205 | moduleModel: Contao\ModuleModel
206 | buffer: string
207 | module: Contao\Module
208 |
209 | getImage:
210 | return_type: ?string
211 | arguments:
212 | originalPath: string
213 | width: int
214 | height: int
215 | mode: string
216 | cacheName: string
217 | file: Contao\File
218 | targetPath: string
219 | imageObject: Contao\Image
220 |
221 | getLanguages:
222 | return_type: void
223 | arguments:
224 | '&compiledLanguages': array
225 | languages: array
226 | langsNative: array
227 | installedOnly: bool
228 |
229 | getPageLayout:
230 | return_type: void
231 | arguments:
232 | pageModel: Contao\PageModel
233 | layout: Contao\LayoutModel
234 | pageRegular: Contao\PageRegular
235 |
236 | getPageStatusIcon:
237 | return_type: string
238 | arguments:
239 | page: object
240 | image: string
241 |
242 | getSystemMessages:
243 | return_type: string
244 | arguments: []
245 |
246 | getUserNavigation:
247 | return_type: array
248 | arguments:
249 | modules: array
250 | showAll: bool
251 |
252 | indexPage:
253 | return_type: void
254 | arguments:
255 | content: string
256 | pageData: array
257 | '&indexData': array
258 |
259 | initializeSystem:
260 | return_type: void
261 | arguments: []
262 |
263 | insertTagFlags:
264 | return_type: ~
265 | arguments:
266 | flag: string
267 | tag: string
268 | cachedValue: string
269 | flags: array
270 | useCache: bool
271 | tags: array
272 | cache: array
273 | _rit: int
274 | _cnt: int
275 |
276 | isAllowedToEditComment:
277 | return_type: bool
278 | arguments:
279 | parentId: int
280 | parentTable: string
281 |
282 | isVisibleElement:
283 | return_type: bool
284 | arguments:
285 | element: Contao\Model
286 | isVisible: bool
287 |
288 | listComments:
289 | return_type: string
290 | arguments:
291 | comments: array
292 |
293 | loadDataContainer:
294 | return_type: void
295 | arguments:
296 | table: string
297 |
298 | loadFormField:
299 | return_type: Contao\Widget
300 | arguments:
301 | widget: Contao\Widget
302 | formId: string
303 | formData: array
304 | form: Contao\Form
305 |
306 | loadLanguageFile:
307 | return_type: void
308 | arguments:
309 | name: string
310 | currentLanguage: string
311 | cacheKey: string
312 |
313 | loadPageDetails:
314 | return_type: void
315 | arguments:
316 | parentModels: array
317 | page: Contao\PageModel
318 |
319 | modifyFrontendPage:
320 | return_type: string
321 | arguments:
322 | buffer: string
323 | templateName: string
324 |
325 | newsListCountItems:
326 | return_type: ~
327 | arguments:
328 | newsArchives: array
329 | featuredOnly: bool
330 | module: Contao\Module
331 |
332 | newsListFetchItems:
333 | return_type: ~
334 | arguments:
335 | newsArchives: array
336 | featuredOnly: ?bool
337 | limit: int
338 | offset: int
339 | module: Contao\Module
340 |
341 | outputBackendTemplate:
342 | return_type: string
343 | arguments:
344 | buffer: string
345 | template: string
346 |
347 | outputFrontendTemplate:
348 | return_type: string
349 | arguments:
350 | buffer: string
351 | template: string
352 |
353 | parseArticles:
354 | return_type: void
355 | arguments:
356 | template: Contao\FrontendTemplate
357 | newsEntry: array
358 | module: Contao\Module
359 |
360 | parseDate:
361 | return_type: string
362 | arguments:
363 | formattedDate: string
364 | format: string
365 | timestamp: ?int
366 |
367 | parseFrontendTemplate:
368 | return_type: string
369 | arguments:
370 | buffer: string
371 | templateName: string
372 | template: Contao\FrontendTemplate
373 |
374 | parseTemplate:
375 | return_type: void
376 | arguments:
377 | template: Contao\Template
378 |
379 | parseWidget:
380 | return_type: string
381 | arguments:
382 | buffer: string
383 | widget: Contao\Widget
384 |
385 | postDownload:
386 | return_type: void
387 | arguments:
388 | file: string
389 |
390 | postUpload:
391 | return_type: void
392 | arguments:
393 | files: array
394 |
395 | prepareFormData:
396 | return_type: void
397 | arguments:
398 | '&submittedData': array
399 | labels: array
400 | fields: array
401 | form: Contao\Form
402 |
403 | printArticleAsPdf:
404 | return_type: void
405 | arguments:
406 | articleContent: string
407 | module: Contao\ModuleArticle
408 |
409 | processFormData:
410 | return_type: void
411 | arguments:
412 | submittedData: array
413 | formData: array
414 | files: ?array
415 | labels: array
416 | form: Contao\Form
417 |
418 | removeOldFeeds:
419 | return_type: array
420 | arguments: []
421 |
422 | removeRecipient:
423 | return_type: void
424 | arguments:
425 | email: string
426 | channels: array
427 |
428 | replaceDynamicScriptTags:
429 | return_type: string
430 | arguments:
431 | buffer: string
432 |
433 | replaceInsertTags:
434 | return_type: ~
435 | arguments:
436 | insertTag: string
437 | useCache: bool
438 | cachedValue: string
439 | flags: array
440 | tags: array
441 | cache: array
442 | _rit: int
443 | _cnt: int
444 |
445 | reviseTable:
446 | return_type: bool
447 | arguments:
448 | table: string
449 | newRecords: ?array
450 | parentTable: ?string
451 | childTables: ?array
452 |
453 | sendNewsletter:
454 | return_type: void
455 | arguments:
456 | email: Contao\Email
457 | newsletter: Contao\Database\Result
458 | recipient: array
459 | text: string
460 | html: string
461 |
462 | setCookie:
463 | return_type: object
464 | arguments:
465 | cookie: object
466 |
467 | setNewPassword:
468 | return_type: void
469 | arguments:
470 | member: ~
471 | password: string
472 | module: [Contao\Module, 'null']
473 |
474 | sqlCompileCommands:
475 | return_type: array
476 | arguments:
477 | sql: array
478 |
479 | sqlGetFromDB:
480 | return_type: array
481 | arguments:
482 | sql: array
483 |
484 | sqlGetFromDca:
485 | return_type: array
486 | arguments:
487 | sql: array
488 |
489 | sqlGetFromFile:
490 | return_type: array
491 | arguments:
492 | sql: array
493 |
494 | storeFormData:
495 | return_type: array
496 | arguments:
497 | data: array
498 | form: Contao\Form
499 |
500 | updatePersonalData:
501 | return_type: void
502 | arguments:
503 | member: Contao\FrontendUser
504 | data: array
505 | module: Contao\Module
506 |
507 | validateFormField:
508 | return_type: Contao\Widget
509 | arguments:
510 | widget: Contao\Widget
511 | formId: string
512 | formData: array
513 | form: Contao\Form
514 |
--------------------------------------------------------------------------------
/config/services.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | contao_maker.generator.class:
3 | class: Contao\MakerBundle\Generator\ClassGenerator
4 | arguments:
5 | - '@maker.generator'
6 |
7 | contao_maker.generator.dca:
8 | class: Contao\MakerBundle\Generator\DcaGenerator
9 | arguments:
10 | - '@maker.file_manager'
11 | - '%kernel.project_dir%'
12 |
13 | contao_maker.generator.language_file:
14 | class: Contao\MakerBundle\Generator\LanguageFileGenerator
15 | arguments:
16 | - '@maker.file_manager'
17 | - '%kernel.project_dir%'
18 |
19 | contao_maker.generator.template:
20 | class: Contao\MakerBundle\Generator\TemplateGenerator
21 | arguments:
22 | - '@maker.generator'
23 |
24 | contao_maker.maker.make_content_element:
25 | class: Contao\MakerBundle\Maker\MakeContentElement
26 | arguments:
27 | - '@contao.framework'
28 | - '@contao_maker.generator.template'
29 | - '@contao_maker.generator.class'
30 | - '@contao_maker.generator.dca'
31 | - '@contao_maker.generator.language_file'
32 | - '@maker.file_manager'
33 | - '%kernel.project_dir%'
34 | tags:
35 | - maker.command
36 |
37 | contao_maker.maker.make_dca_callback:
38 | class: Contao\MakerBundle\Maker\MakeDcaCallback
39 | arguments:
40 | - '@contao.framework'
41 | - '@contao_maker.generator.class'
42 | - '@contao.resource_finder'
43 | - '@contao_maker.reflection.signature_generator'
44 | - '@contao_maker.reflection.import_extractor'
45 | tags:
46 | - maker.command
47 |
48 | contao_maker.maker.make_event_listener:
49 | class: Contao\MakerBundle\Maker\MakeEventListener
50 | arguments:
51 | - '@contao_maker.generator.class'
52 | - '@contao_maker.reflection.signature_generator'
53 | - '@contao_maker.reflection.import_extractor'
54 | tags:
55 | - maker.command
56 |
57 | contao_maker.maker.make_frontend_module:
58 | class: Contao\MakerBundle\Maker\MakeFrontendModule
59 | arguments:
60 | - '@contao.framework'
61 | - '@contao_maker.generator.template'
62 | - '@contao_maker.generator.class'
63 | - '@contao_maker.generator.dca'
64 | - '@contao_maker.generator.language_file'
65 | - '@maker.file_manager'
66 | - '%kernel.project_dir%'
67 | tags:
68 | - maker.command
69 |
70 | contao_maker.maker.make_hook:
71 | class: Contao\MakerBundle\Maker\MakeHook
72 | arguments:
73 | - '@contao_maker.generator.class'
74 | - '@contao_maker.reflection.signature_generator'
75 | - '@contao_maker.reflection.import_extractor'
76 | tags:
77 | - maker.command
78 |
79 | contao_maker.reflection.import_extractor:
80 | class: Contao\MakerBundle\Reflection\ImportExtractor
81 |
82 | contao_maker.reflection.signature_generator:
83 | class: Contao\MakerBundle\Reflection\SignatureGenerator
84 |
--------------------------------------------------------------------------------
/skeleton/content-element/ContentElement.tpl.php:
--------------------------------------------------------------------------------
1 | = "
2 |
3 | declare(strict_types=1);
4 |
5 | namespace App\Controller\ContentElement;
6 |
7 | use Contao\ContentModel;
8 | use Contao\CoreBundle\Controller\ContentElement\AbstractContentElementController;
9 | use Contao\CoreBundle\DependencyInjection\Attribute\AsContentElement;
10 | use Contao\CoreBundle\Twig\FragmentTemplate;
11 | use Symfony\Component\HttpFoundation\Request;
12 | use Symfony\Component\HttpFoundation\Response;
13 |
14 | #[AsContentElement(category: '= $category ?>')]
15 | class = $className ?> extends AbstractContentElementController
16 | {
17 | protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response
18 | {
19 | return $template->getResponse();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/skeleton/content-element/content_element.tpl.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@Contao/content_element/_base.html.twig" %}
2 |
3 | {% block content %}
4 |
Put your content here.
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/skeleton/content-element/tl_content.tpl.php:
--------------------------------------------------------------------------------
1 |
2 | = "
3 |
4 | declare(strict_types=1);
5 |
6 |
7 | $GLOBALS['TL_DCA']['tl_content']['palettes']['= $element_name ?>'] = '
8 | {type_legend},type,headline;
9 | {template_legend:hide},customTpl;
10 | {protected_legend:hide},protected;
11 | {expert_legend:hide},cssID;
12 | {invisible_legend:hide},invisible,start,stop
13 | ';
14 |
--------------------------------------------------------------------------------
/skeleton/dca-callback/Callback.tpl.php:
--------------------------------------------------------------------------------
1 | = "
2 |
3 | declare(strict_types=1);
4 |
5 | namespace App\EventListener\DataContainer;
6 |
7 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback;
8 |
9 | use = $use ?>;
10 |
11 |
12 | #[AsCallback(table: '= $table ?>', target: '= $target ?>')]
13 | class = $className."\n" ?>
14 | {
15 | = $signature."\n" ?>
16 | {
17 | = $body."\n" ?>
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/skeleton/event-listener/EventListener.tpl.php:
--------------------------------------------------------------------------------
1 | = "
2 |
3 | declare(strict_types=1);
4 |
5 | namespace App\EventListener;
6 |
7 |
8 | use = $use ?>;
9 |
10 | use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
11 |
12 | #[AsEventListener('= $event ?>')]
13 | class = $className."\n" ?>
14 | {
15 | = $signature."\n" ?>
16 | {
17 | = $body."\n" ?>
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/skeleton/frontend-module/FrontendModule.tpl.php:
--------------------------------------------------------------------------------
1 | = "
2 |
3 | declare(strict_types=1);
4 |
5 | namespace App\Controller\FrontendModule;
6 |
7 | use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
8 | use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
9 | use Contao\ModuleModel;
10 | use Contao\CoreBundle\Twig\FragmentTemplate;
11 | use Symfony\Component\HttpFoundation\Request;
12 | use Symfony\Component\HttpFoundation\Response;
13 |
14 | #[AsFrontendModule(category: '= $category ?>')]
15 | class = $className ?> extends AbstractFrontendModuleController
16 | {
17 | protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
18 | {
19 | return $template->getResponse();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/skeleton/frontend-module/frontend_module.tpl.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@Contao/frontend_module/_base.html.twig" %}
2 |
3 | {% block content %}
4 | Put your content here.
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/skeleton/frontend-module/tl_module.tpl.php:
--------------------------------------------------------------------------------
1 |
2 | = "
3 |
4 | declare(strict_types=1);
5 |
6 |
7 | $GLOBALS['TL_DCA']['tl_module']['palettes']['= $element_name ?>'] = '
8 | {title_legend},name,headline,type;
9 | {template_legend:hide},customTpl;
10 | {protected_legend:hide},protected;
11 | {expert_legend:hide},cssID
12 | ';
13 |
--------------------------------------------------------------------------------
/skeleton/hook/Hook.tpl.php:
--------------------------------------------------------------------------------
1 | = "
2 |
3 | declare(strict_types=1);
4 |
5 | namespace App\EventListener;
6 |
7 | use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
8 |
9 | use = $use ?>;
10 |
11 |
12 | #[AsHook('= $hook ?>')]
13 | class = $className."\n" ?>
14 | {
15 | = $signature."\n" ?>
16 | {
17 | = $body."\n" ?>
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/ContaoMakerBundle.php:
--------------------------------------------------------------------------------
1 | setLoadAfter([MakerBundle::class]),
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/DependencyInjection/ContaoMakerExtension.php:
--------------------------------------------------------------------------------
1 | load('services.yaml');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Generator/ClassGenerator.php:
--------------------------------------------------------------------------------
1 | getOptionsResolver()->resolve($options);
28 |
29 | return $this->generator->generateClass(
30 | $options['fqcn'],
31 | $this->getSourcePath($options['source']),
32 | $options['variables'],
33 | );
34 | }
35 |
36 | private function getOptionsResolver(): OptionsResolver
37 | {
38 | $resolver = new OptionsResolver();
39 | $resolver->setRequired(['fqcn', 'source']);
40 | $resolver->setDefaults(['variables' => []]);
41 |
42 | return $resolver;
43 | }
44 |
45 | private function getSourcePath(string $path): string
46 | {
47 | return Path::join(__DIR__.'/../../skeleton', $path);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Generator/DcaGenerator.php:
--------------------------------------------------------------------------------
1 | getOptionsResolver()->resolve($options);
30 |
31 | $source = $this->getSourcePath($options['source']);
32 | $target = Path::join($this->projectDir, 'contao/dca', $options['domain'].'.php');
33 | $fileExists = $this->fileManager->fileExists($target);
34 |
35 | $variables = [
36 | 'append' => $fileExists,
37 | 'element_name' => $options['element'],
38 | ...$options['variables'],
39 | ];
40 |
41 | $contents = $this->fileManager->parseTemplate($source, $variables);
42 | $contents = ltrim($contents);
43 |
44 | if ($fileExists) {
45 | $contents = file_get_contents($target)."\n".rtrim($contents)."\n";
46 | }
47 |
48 | $this->fileManager->dumpFile($target, $contents);
49 |
50 | return Path::join('contao/dca', $options['domain'].'.php');
51 | }
52 |
53 | private function getOptionsResolver(): OptionsResolver
54 | {
55 | $resolver = new OptionsResolver();
56 | $resolver->setRequired(['domain', 'source', 'element']);
57 | $resolver->setDefaults(['variables' => []]);
58 |
59 | return $resolver;
60 | }
61 |
62 | private function getSourcePath(string $path): string
63 | {
64 | return Path::join(__DIR__.'/../../skeleton', $path);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Generator/GeneratorInterface.php:
--------------------------------------------------------------------------------
1 | $options
19 | */
20 | public function generate(array $options): string;
21 | }
22 |
--------------------------------------------------------------------------------
/src/Generator/LanguageFileGenerator.php:
--------------------------------------------------------------------------------
1 | getOptionsResolver()->resolve($options);
31 | $target = Path::join($this->projectDir, 'translations', \sprintf('%s.%s.yaml', $options['domain'], $options['language']));
32 |
33 | if ($this->fileManager->fileExists($target)) {
34 | $translations = Yaml::parse($this->fileManager->getFileContents($target));
35 | } else {
36 | $translations = [];
37 | }
38 |
39 | $translations = array_merge_recursive($translations, $options['variables']);
40 |
41 | $this->fileManager->dumpFile($target, Yaml::dump($translations, inline: 10));
42 |
43 | return Path::makeRelative($target, $this->projectDir);
44 | }
45 |
46 | private function getOptionsResolver(): OptionsResolver
47 | {
48 | $resolver = new OptionsResolver();
49 | $resolver->setRequired(['domain', 'language', 'variables']);
50 | $resolver->setAllowedTypes('variables', ['array']);
51 |
52 | return $resolver;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Generator/TemplateGenerator.php:
--------------------------------------------------------------------------------
1 | getOptionsResolver()->resolve($options);
28 |
29 | $this->generator->generateFile(
30 | $options['target'],
31 | $this->getSourcePath($options['source']),
32 | $options['variables'],
33 | );
34 |
35 | return $options['target'];
36 | }
37 |
38 | private function getOptionsResolver(): OptionsResolver
39 | {
40 | $resolver = new OptionsResolver();
41 | $resolver->setRequired(['target', 'source']);
42 | $resolver->setDefaults(['variables' => []]);
43 |
44 | return $resolver;
45 | }
46 |
47 | private function getSourcePath(string $path): string
48 | {
49 | return Path::join(__DIR__.'/../../skeleton', $path);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Maker/AbstractFragmentMaker.php:
--------------------------------------------------------------------------------
1 | askForCategory($input, $io, $command);
51 | $this->askForDcaPalette($input, $io, $command);
52 | $this->askForTranslation($input, $io, $command);
53 |
54 | if ($input->getArgument('add-translation')) {
55 | $this->askForSourceName($input, $io, $command);
56 | $this->askForSourceDescription($input, $io, $command);
57 |
58 | $i = 0;
59 |
60 | while (true) {
61 | $this->askForAdditionalTranslation($input, $io, $command, $i);
62 |
63 | if (!$input->getArgument('add-translation-'.$i)) {
64 | break;
65 | }
66 |
67 | $this->askForLanguage($input, $io, $command, $i);
68 | $this->askForTargetName($input, $io, $command, $i);
69 | $this->askForTargetDescription($input, $io, $command, $i);
70 |
71 | ++$i;
72 | }
73 | }
74 | }
75 |
76 | public function configureDependencies(DependencyBuilder $dependencies): void
77 | {
78 | }
79 |
80 | abstract protected function getGlobalsRegistryKey(): string;
81 |
82 | abstract protected function getTemplatePrefix(): string;
83 |
84 | protected function getTemplateName(string $className): string
85 | {
86 | return Path::join(
87 | $this->projectDir,
88 | 'contao/templates',
89 | $this->getTemplatePrefix(),
90 | \sprintf('%s.html.twig', Container::underscore($className)),
91 | );
92 | }
93 |
94 | /**
95 | * @return array
96 | */
97 | protected function getCategories(): array
98 | {
99 | $this->framework->initialize();
100 |
101 | return array_keys((array) $GLOBALS[$this->getGlobalsRegistryKey()]);
102 | }
103 |
104 | protected function getClassNameWithoutSuffix(string $className): string
105 | {
106 | if (str_ends_with($className, 'Controller')) {
107 | $className = substr($className, 0, -10);
108 | }
109 |
110 | return $className;
111 | }
112 |
113 | private function askForCategory(InputInterface $input, ConsoleStyle $io, Command $command): void
114 | {
115 | $command->addArgument('category', InputArgument::REQUIRED);
116 |
117 | $categories = $this->getCategories();
118 |
119 | $io->writeln(' Suggested categories:>');
120 | $io->listing($categories);
121 |
122 | $attributeClass = match (static::class) {
123 | MakeContentElement::class => AsContentElement::class,
124 | MakeFrontendModule::class => AsFrontendModule::class,
125 | default => null,
126 | };
127 |
128 | $default = null;
129 |
130 | if ($attributeClass) {
131 | $reflection = new \ReflectionClass($attributeClass);
132 | $params = $reflection->getConstructor()->getParameters();
133 |
134 | foreach ($params as $param) {
135 | if ('category' === $param->getName()) {
136 | $default = $param->getDefaultValue();
137 |
138 | break;
139 | }
140 | }
141 | }
142 |
143 | $question = new Question('Choose a category', $default);
144 | $question->setAutocompleterValues($categories);
145 |
146 | $category = (string) $io->askQuestion($question);
147 |
148 | $input->setArgument('category', $category === (string) $default ? null : $category);
149 | }
150 |
151 | private function askForDcaPalette(InputInterface $input, ConsoleStyle $io, Command $command): void
152 | {
153 | $command->addArgument('add-palette', InputArgument::REQUIRED);
154 |
155 | $question = new ConfirmationQuestion('Do you want to add a palette?');
156 |
157 | $input->setArgument('add-palette', $io->askQuestion($question));
158 | }
159 |
160 | private function askForTranslation(InputInterface $input, ConsoleStyle $io, Command $command): void
161 | {
162 | $command->addArgument('add-translation', InputArgument::REQUIRED);
163 |
164 | $question = new ConfirmationQuestion('Do you want to add a translation?');
165 |
166 | $input->setArgument('add-translation', $io->askQuestion($question));
167 | }
168 |
169 | private function askForSourceName(InputInterface $input, ConsoleStyle $io, Command $command): void
170 | {
171 | $command->addArgument('source-name', InputArgument::OPTIONAL);
172 |
173 | $question = new Question('Enter the English name');
174 | $question->setValidator(Validator::notBlank(...));
175 |
176 | $input->setArgument('source-name', $io->askQuestion($question));
177 | }
178 |
179 | private function askForSourceDescription(InputInterface $input, ConsoleStyle $io, Command $command): void
180 | {
181 | $command->addArgument('source-description', InputArgument::OPTIONAL);
182 |
183 | $question = new Question('Enter the English description');
184 | $question->setValidator(Validator::notBlank(...));
185 |
186 | $input->setArgument('source-description', $io->askQuestion($question));
187 | }
188 |
189 | private function askForAdditionalTranslation(InputInterface $input, ConsoleStyle $io, Command $command, int $count): void
190 | {
191 | $command->addArgument('add-translation-'.$count, InputArgument::OPTIONAL);
192 |
193 | $question = new ConfirmationQuestion('Do you want to add another translation?', false);
194 |
195 | $input->setArgument('add-translation-'.$count, $io->askQuestion($question));
196 | }
197 |
198 | private function askForLanguage(InputInterface $input, ConsoleStyle $io, Command $command, int $count): void
199 | {
200 | $command->addArgument('language-'.$count, InputArgument::OPTIONAL);
201 |
202 | $question = new Question('Which language do you want to add? (e.g. de>)');
203 | $question->setValidator(Validator::notBlank(...));
204 |
205 | $input->setArgument('language-'.$count, $io->askQuestion($question));
206 | }
207 |
208 | private function askForTargetName(InputInterface $input, ConsoleStyle $io, Command $command, int $count): void
209 | {
210 | $command->addArgument('target-name-'.$count, InputArgument::OPTIONAL);
211 |
212 | $question = new Question('Enter the translated name');
213 | $question->setValidator(Validator::notBlank(...));
214 |
215 | $input->setArgument('target-name-'.$count, $io->askQuestion($question));
216 | }
217 |
218 | private function askForTargetDescription(InputInterface $input, ConsoleStyle $io, Command $command, int $count): void
219 | {
220 | $command->addArgument('target-description-'.$count, InputArgument::OPTIONAL);
221 |
222 | $question = new Question('Enter the translated description');
223 | $question->setValidator(Validator::notBlank(...));
224 |
225 | $input->setArgument('target-description-'.$count, $io->askQuestion($question));
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/src/Maker/MakeContentElement.php:
--------------------------------------------------------------------------------
1 | addArgument('element-class', InputArgument::REQUIRED, \sprintf('Enter a class name for the element controller (e.g. %sController>)', Str::asClassName(Str::getRandomTerm())))
41 | ;
42 | }
43 |
44 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
45 | {
46 | $category = $input->getArgument('category');
47 | $addPalette = $input->getArgument('add-palette');
48 | $addTranslation = $input->getArgument('add-translation');
49 | $name = $input->getArgument('element-class');
50 |
51 | $className = Str::asClassName($name);
52 | $classNameWithoutSuffix = $this->getClassNameWithoutSuffix($className);
53 | $elementName = Container::underscore($classNameWithoutSuffix);
54 | $elementDetails = $generator->createClassNameDetails($name, 'Controller\ContentElement\\');
55 |
56 | $this->classGenerator->generate([
57 | 'source' => 'content-element/ContentElement.tpl.php',
58 | 'fqcn' => $elementDetails->getFullName(),
59 | 'variables' => [
60 | 'className' => $elementDetails->getShortName(),
61 | 'elementName' => $elementName,
62 | 'category' => $category,
63 | ],
64 | ]);
65 |
66 | $this->templateGenerator->generate([
67 | 'source' => 'content-element/content_element.tpl.html.twig',
68 | 'target' => $this->getTemplateName($classNameWithoutSuffix),
69 | ]);
70 |
71 | $twigRoot = Path::join($this->projectDir, 'contao/templates/.twig-root');
72 |
73 | if (!$this->fileManager->fileExists($twigRoot)) {
74 | $this->fileManager->dumpFile($twigRoot, '');
75 | }
76 |
77 | if ($addPalette) {
78 | $this->dcaGenerator->generate([
79 | 'source' => 'content-element/tl_content.tpl.php',
80 | 'domain' => 'tl_content',
81 | 'element' => $elementName,
82 | ]);
83 | }
84 |
85 | if ($addTranslation) {
86 | $this->languageFileGenerator->generate([
87 | 'domain' => 'contao_default',
88 | 'language' => 'en',
89 | 'variables' => [
90 | 'CTE' => [
91 | $elementName => [
92 | $input->getArgument('source-name'),
93 | $input->getArgument('source-description'),
94 | ],
95 | ],
96 | ],
97 | ]);
98 |
99 | $i = 0;
100 |
101 | while (true) {
102 | $hasNext = $input->hasArgument('add-translation-'.$i);
103 |
104 | if (!$hasNext || false === $input->getArgument('add-translation-'.$i)) {
105 | break;
106 | }
107 |
108 | $this->languageFileGenerator->generate([
109 | 'domain' => 'contao_default',
110 | 'language' => $input->getArgument('language-'.$i),
111 | 'variables' => [
112 | 'CTE' => [
113 | $elementName => [
114 | $input->getArgument('target-name-'.$i),
115 | $input->getArgument('target-description-'.$i),
116 | ],
117 | ],
118 | ],
119 | ]);
120 |
121 | ++$i;
122 | }
123 | }
124 |
125 | $generator->writeChanges();
126 |
127 | $this->writeSuccessMessage($io);
128 | }
129 |
130 | protected function getGlobalsRegistryKey(): string
131 | {
132 | return 'TL_CTE';
133 | }
134 |
135 | protected function getTemplatePrefix(): string
136 | {
137 | return 'content_element';
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Maker/MakeDcaCallback.php:
--------------------------------------------------------------------------------
1 | addArgument('callback-class', InputArgument::REQUIRED, \sprintf('Enter a class name for the callback (e.g. %sListener>)', Str::asClassName(Str::getRandomTerm())))
60 | ;
61 | }
62 |
63 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
64 | {
65 | $this->askForTable($input, $io, $command);
66 | $this->askForTarget($input, $io, $command);
67 | }
68 |
69 | public function configureDependencies(DependencyBuilder $dependencies): void
70 | {
71 | }
72 |
73 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
74 | {
75 | $table = $input->getArgument('table');
76 | $target = $input->getArgument('target');
77 | $name = $input->getArgument('callback-class');
78 |
79 | $targets = $this->getTargets();
80 |
81 | if (!\array_key_exists($target, $targets)) {
82 | $io->error('Invalid DCA callback: '.$target);
83 |
84 | return;
85 | }
86 |
87 | $definition = $targets[$target];
88 | $elementDetails = $generator->createClassNameDetails($name, 'EventListener\DataContainer\\');
89 |
90 | if (str_contains((string) $target, '{')) {
91 | $chunks = explode('.', (string) $target);
92 |
93 | foreach ($chunks as $chunk) {
94 | if ('{' === $chunk[0]) {
95 | $target = str_replace($chunk, $input->getArgument($chunk), $target);
96 | }
97 | }
98 | }
99 |
100 | $this->classGenerator->generate([
101 | 'source' => 'dca-callback/Callback.tpl.php',
102 | 'fqcn' => $elementDetails->getFullName(),
103 | 'variables' => [
104 | 'uses' => $this->importExtractor->extract($definition),
105 | 'table' => $table,
106 | 'target' => $target,
107 | 'className' => $elementDetails->getShortName(),
108 | 'signature' => $this->signatureGenerator->generate($definition, '__invoke'),
109 | 'body' => $definition->getBody(),
110 | ],
111 | ]);
112 |
113 | $generator->writeChanges();
114 |
115 | $this->writeSuccessMessage($io);
116 | }
117 |
118 | private function askForTable(InputInterface $input, ConsoleStyle $io, Command $command): void
119 | {
120 | $command->addArgument('table', InputArgument::REQUIRED);
121 |
122 | $tables = $this->getTables();
123 |
124 | $io->writeln(' Suggested tables:>');
125 | $io->listing($tables);
126 |
127 | $question = new Question('Enter a table for the callback');
128 | $question->setAutocompleterValues($tables);
129 | $question->setValidator(Validator::notBlank(...));
130 |
131 | $input->setArgument('table', $io->askQuestion($question));
132 | }
133 |
134 | private function askForTarget(InputInterface $input, ConsoleStyle $io, Command $command): void
135 | {
136 | $command->addArgument('target', InputArgument::REQUIRED);
137 |
138 | $targets = $this->getTargets();
139 |
140 | $io->writeln(' Suggested targets:>');
141 | $io->listing(array_keys($targets));
142 |
143 | $question = new Question('Enter a target for the callback');
144 | $question->setAutocompleterValues(array_keys($targets));
145 | $question->setValidator(Validator::notBlank(...));
146 |
147 | $target = $io->askQuestion($question);
148 |
149 | if (str_contains((string) $target, '{')) {
150 | $chunks = explode('.', (string) $target);
151 |
152 | foreach ($chunks as $chunk) {
153 | if ('{' !== $chunk[0]) {
154 | continue;
155 | }
156 |
157 | $command->addArgument($chunk, InputArgument::OPTIONAL);
158 |
159 | $question = new Question(\sprintf('Please enter a value for "%s"', $chunk));
160 | $question->setValidator(Validator::notBlank(...));
161 |
162 | $input->setArgument($chunk, $io->askQuestion($question));
163 | }
164 | }
165 |
166 | $input->setArgument('target', $target);
167 | }
168 |
169 | /**
170 | * @return array
171 | */
172 | private function getTables(): array
173 | {
174 | $this->framework->initialize();
175 |
176 | $files = $this->resourceFinder->findIn('dca')->depth(0)->files()->name('*.php');
177 |
178 | $tables = array_map(
179 | static fn (SplFileInfo $input) => str_replace('.php', '', $input->getRelativePathname()),
180 | iterator_to_array($files->getIterator()),
181 | );
182 |
183 | $tables = array_values($tables);
184 |
185 | return array_unique($tables);
186 | }
187 |
188 | /**
189 | * @return array
190 | */
191 | private function getTargets(): array
192 | {
193 | $yaml = Yaml::parseFile(__DIR__.'/../../config/callbacks.yaml');
194 | $targets = [];
195 |
196 | foreach ($yaml['callbacks'] as $key => $config) {
197 | $targets[$key] = new MethodDefinition($config['return_type'], $config['arguments'], $config['body'] ?? null);
198 | }
199 |
200 | return $targets;
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/Maker/MakeEventListener.php:
--------------------------------------------------------------------------------
1 | addArgument('event-class', InputArgument::OPTIONAL, \sprintf('Enter a class name for the listener (e.g. %sListener>)', Str::asClassName(Str::getRandomTerm())))
54 | ;
55 | }
56 |
57 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
58 | {
59 | $command->addArgument('event', InputArgument::OPTIONAL);
60 |
61 | $events = $this->getAvailableEvents();
62 |
63 | $io->writeln(' Available events:>');
64 | $io->listing(array_keys($events));
65 |
66 | $question = new Question('Choose the event to listen for');
67 | $question->setAutocompleterValues(array_keys($events));
68 |
69 | $input->setArgument('event', $io->askQuestion($question));
70 | }
71 |
72 | public function configureDependencies(DependencyBuilder $dependencies): void
73 | {
74 | }
75 |
76 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
77 | {
78 | $event = $input->getArgument('event');
79 | $name = $input->getArgument('event-class');
80 | $events = $this->getAvailableEvents();
81 |
82 | if (!\array_key_exists($event, $events)) {
83 | $io->error('Invalid event name: '.$event);
84 |
85 | return;
86 | }
87 |
88 | $definition = $events[$event];
89 | $elementDetails = $generator->createClassNameDetails($name, 'EventListener\\');
90 |
91 | $this->classGenerator->generate([
92 | 'source' => 'event-listener/EventListener.tpl.php',
93 | 'fqcn' => $elementDetails->getFullName(),
94 | 'variables' => [
95 | 'uses' => $this->importExtractor->extract($definition),
96 | 'event' => $event,
97 | 'className' => $elementDetails->getShortName(),
98 | 'signature' => $this->signatureGenerator->generate($definition, '__invoke'),
99 | 'body' => $definition->getBody(),
100 | ],
101 | ]);
102 |
103 | $generator->writeChanges();
104 |
105 | $this->writeSuccessMessage($io);
106 | }
107 |
108 | /**
109 | * @return array
110 | */
111 | private function getAvailableEvents(): array
112 | {
113 | $yaml = Yaml::parseFile(__DIR__.'/../../config/events.yaml');
114 | $events = [];
115 |
116 | foreach ($yaml['events'] as $key => $config) {
117 | $events[$key] = new MethodDefinition($config['return_type'] ?? null, $config['arguments'] ?? [], $config['body'] ?? null);
118 | }
119 |
120 | return $events;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Maker/MakeFrontendModule.php:
--------------------------------------------------------------------------------
1 | addArgument('module-class', InputArgument::REQUIRED, \sprintf('Enter a class name for the module controller (e.g. %sController>)', Str::asClassName(Str::getRandomTerm())))
42 | ;
43 | }
44 |
45 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
46 | {
47 | $category = $input->getArgument('category');
48 | $addPalette = $input->getArgument('add-palette');
49 | $addTranslation = $input->getArgument('add-translation');
50 | $name = $input->getArgument('module-class');
51 |
52 | $className = Str::asClassName($name);
53 | $classNameWithoutSuffix = $this->getClassNameWithoutSuffix($className);
54 | $elementName = Container::underscore($classNameWithoutSuffix);
55 | $elementDetails = $generator->createClassNameDetails($name, 'Controller\\FrontendModule\\');
56 |
57 | $this->classGenerator->generate([
58 | 'source' => 'frontend-module/FrontendModule.tpl.php',
59 | 'fqcn' => $elementDetails->getFullName(),
60 | 'variables' => [
61 | 'className' => $elementDetails->getShortName(),
62 | 'elementName' => $elementName,
63 | 'category' => $category,
64 | ],
65 | ]);
66 |
67 | $this->templateGenerator->generate([
68 | 'source' => 'frontend-module/frontend_module.tpl.html.twig',
69 | 'target' => $this->getTemplateName($classNameWithoutSuffix),
70 | ]);
71 |
72 | $twigRoot = Path::join($this->projectDir, 'contao/templates/.twig-root');
73 |
74 | if (!$this->fileManager->fileExists($twigRoot)) {
75 | $this->fileManager->dumpFile($twigRoot, '');
76 | }
77 |
78 | if ($addPalette) {
79 | $this->dcaGenerator->generate([
80 | 'source' => 'frontend-module/tl_module.tpl.php',
81 | 'domain' => 'tl_module',
82 | 'element' => $elementName,
83 | ]);
84 | }
85 |
86 | if ($addTranslation) {
87 | $this->languageFileGenerator->generate([
88 | 'domain' => 'contao_modules',
89 | 'language' => 'en',
90 | 'variables' => [
91 | 'FMD' => [
92 | $elementName => [
93 | $input->getArgument('source-name'),
94 | $input->getArgument('source-description'),
95 | ],
96 | ],
97 | ],
98 | ]);
99 |
100 | $i = 0;
101 |
102 | while (true) {
103 | $hasNext = $input->hasArgument('add-translation-'.$i);
104 |
105 | if (!$hasNext || false === $input->getArgument('add-translation-'.$i)) {
106 | break;
107 | }
108 |
109 | $this->languageFileGenerator->generate([
110 | 'domain' => 'contao_modules',
111 | 'language' => $input->getArgument('language-'.$i),
112 | 'variables' => [
113 | 'FMD' => [
114 | $elementName => [
115 | $input->getArgument('target-name-'.$i),
116 | $input->getArgument('target-description-'.$i),
117 | ],
118 | ],
119 | ],
120 | ]);
121 |
122 | ++$i;
123 | }
124 | }
125 |
126 | $generator->writeChanges();
127 |
128 | $this->writeSuccessMessage($io);
129 | }
130 |
131 | public function configureDependencies(DependencyBuilder $dependencies): void
132 | {
133 | }
134 |
135 | protected function getGlobalsRegistryKey(): string
136 | {
137 | return 'FE_MOD';
138 | }
139 |
140 | protected function getTemplatePrefix(): string
141 | {
142 | return 'frontend_module';
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/Maker/MakeHook.php:
--------------------------------------------------------------------------------
1 | addArgument('hook-class', InputArgument::REQUIRED, \sprintf('Enter a class name for the listener (e.g. %sListener>)', Str::asClassName(Str::getRandomTerm())))
55 | ;
56 | }
57 |
58 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
59 | {
60 | $command->addArgument('hook', InputArgument::REQUIRED);
61 |
62 | $hooks = $this->getAvailableHooks();
63 |
64 | $io->writeln(' Available hooks:>');
65 | $io->listing(array_keys($hooks));
66 |
67 | $question = new Question('Choose the hook to listen for');
68 | $question->setAutocompleterValues(array_keys($hooks));
69 | $question->setValidator(Validator::notBlank(...));
70 |
71 | $input->setArgument('hook', $io->askQuestion($question));
72 | }
73 |
74 | public function configureDependencies(DependencyBuilder $dependencies): void
75 | {
76 | }
77 |
78 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
79 | {
80 | $hook = $input->getArgument('hook');
81 | $name = $input->getArgument('hook-class');
82 | $hooks = $this->getAvailableHooks();
83 |
84 | if (!\array_key_exists($hook, $hooks)) {
85 | $io->error('Invalid hook name: '.$hook);
86 |
87 | return;
88 | }
89 |
90 | $definition = $hooks[$hook];
91 | $elementDetails = $generator->createClassNameDetails($name, 'EventListener\\');
92 |
93 | $this->classGenerator->generate([
94 | 'source' => 'hook/Hook.tpl.php',
95 | 'fqcn' => $elementDetails->getFullName(),
96 | 'variables' => [
97 | 'uses' => $this->importExtractor->extract($definition),
98 | 'hook' => $hook,
99 | 'className' => $elementDetails->getShortName(),
100 | 'signature' => $this->signatureGenerator->generate($definition, '__invoke'),
101 | 'body' => $definition->getBody(),
102 | ],
103 | ]);
104 |
105 | $generator->writeChanges();
106 |
107 | $this->writeSuccessMessage($io);
108 | }
109 |
110 | /**
111 | * @return array
112 | */
113 | private function getAvailableHooks(): array
114 | {
115 | $yaml = Yaml::parseFile(__DIR__.'/../../config/hooks.yaml');
116 | $hooks = [];
117 |
118 | foreach ($yaml['hooks'] as $key => $config) {
119 | $hooks[$key] = new MethodDefinition($config['return_type'] ?? null, $config['arguments'] ?? [], $config['body'] ?? null);
120 | }
121 |
122 | return $hooks;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Reflection/ImportExtractor.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public function extract(MethodDefinition $method): array
21 | {
22 | $objectTypeHints = [];
23 |
24 | foreach ($method->getParameters() as $parameter) {
25 | if (null === $parameter) {
26 | continue;
27 | }
28 |
29 | $type = \is_array($parameter) ? $parameter[0] : $parameter;
30 |
31 | if (!class_exists((string) $type)) {
32 | continue;
33 | }
34 |
35 | $objectTypeHints[] = $type;
36 | }
37 |
38 | $returnType = $method->getReturnType();
39 |
40 | // If a return type is set, check if the class exists and add it to our imports
41 | if (null !== $returnType && class_exists($returnType)) {
42 | $objectTypeHints[] = $returnType;
43 | }
44 |
45 | $objectTypeHints = array_unique($objectTypeHints);
46 | sort($objectTypeHints);
47 |
48 | return $objectTypeHints;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Reflection/MethodDefinition.php:
--------------------------------------------------------------------------------
1 | $parameters
19 | */
20 | public function __construct(
21 | private readonly string|null $returnType,
22 | private readonly array $parameters,
23 | private readonly string|null $body = null,
24 | ) {
25 | }
26 |
27 | public function getReturnType(): string|null
28 | {
29 | return $this->returnType;
30 | }
31 |
32 | /**
33 | * @return array
34 | */
35 | public function getParameters(): array
36 | {
37 | return $this->parameters;
38 | }
39 |
40 | public function getBody(): string
41 | {
42 | if (null !== $this->body) {
43 | return $this->body;
44 | }
45 |
46 | return match ($this->returnType) {
47 | 'string' => "return '';",
48 | '?string' => 'return null;',
49 | 'array' => 'return [];',
50 | 'bool' => 'return true;',
51 | default => '// Do something',
52 | };
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Reflection/SignatureGenerator.php:
--------------------------------------------------------------------------------
1 | getParameters() as $name => $type) {
24 | $defaultValue = null;
25 |
26 | if (\is_array($type)) {
27 | [$type, $defaultValue] = $type;
28 | }
29 |
30 | $paramName = str_replace('&', '', $name);
31 | [$paramType] = \is_array($type) ? $type : [$type, null];
32 |
33 | if (null !== $paramType && class_exists($paramType)) {
34 | $paramType = Str::getShortClassName($paramType);
35 | }
36 |
37 | $paramReference = str_starts_with($name, '&');
38 | $parameterTemplate = \sprintf('%s %s$%s', $paramType, $paramReference ? '&' : '', $paramName);
39 |
40 | if (null !== $defaultValue) {
41 | $parameterTemplate = \sprintf('%s = %s', $parameterTemplate, $defaultValue);
42 | }
43 |
44 | $parameterTemplate = trim($parameterTemplate);
45 | $parameterTemplates[] = $parameterTemplate;
46 | }
47 |
48 | return \sprintf(
49 | 'public function %s(%s)%s',
50 | $methodName,
51 | implode(', ', $parameterTemplates),
52 | $this->getReturnType($method),
53 | );
54 | }
55 |
56 | private function getReturnType(MethodDefinition $method): string
57 | {
58 | $returnType = $method->getReturnType();
59 |
60 | if (null !== $returnType && class_exists($returnType)) {
61 | $returnType = Str::getShortClassName($returnType);
62 | }
63 |
64 | return $returnType ? ': '.$returnType : '';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------