├── .editorconfig
├── LICENSE
├── README.md
├── RoboFile.php
├── assets
└── init
│ ├── .editorconfig
│ ├── .gitignore
│ ├── .php-cs-fixer.dist.php
│ ├── RoboFile.php
│ ├── jorobo.dist.ini
│ ├── phpstan.neon
│ ├── phpunit.xml.dist
│ └── ruleset.xml
├── bin
└── jorobo
├── composer.json
├── composer.lock
├── jorobo.dist.ini
├── jorobo.ini
└── src
├── Command
└── InitCommand.php
└── Tasks
├── AssetJSON.php
├── Build.php
├── Build
├── Base.php
├── Component.php
├── Extension.php
├── File.php
├── Language.php
├── Library.php
├── Media.php
├── Module.php
├── Package.php
├── Plugin.php
├── Tasks.php
└── Template.php
├── BumpVersion.php
├── CopyrightHeader.php
├── Deploy
├── Base.php
├── FtpUpload.php
├── Package.php
├── Release.php
├── Tasks.php
└── Zip.php
├── Generate.php
├── Generate
├── Base.php
├── Component.php
├── Module.php
├── Package.php
├── Plugin.php
├── Tasks.php
└── Template.php
├── JTask.php
├── Map.php
└── Tasks.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style end of lines and a blank line at the end of the file
7 | [*]
8 | indent_style = tab
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [*.php]
15 | indent_style = space
16 | indent_size = 4
17 |
18 | [*.{js,json,scss,css,yml,vue}]
19 | indent_style = space
20 | indent_size = 2
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JoRobo (Robo.li tasks for Joomla!)
2 |
3 | [](https://packagist.org/packages/joomla-projects/jorobo) [](https://packagist.org/packages/joomla-projects/jorobo) [](https://packagist.org/packages/joomla-projects/jorobo)
4 |
5 | Tools and Tasks based on [Robo.li](https://robo.li) for Joomla Extension Development and Releases
6 |
7 | ## Installation (Standalone):
8 |
9 | * `composer require joomla-projects/jorobo`
10 | * configure jorobo.ini
11 | * `vendor/bin/robo`
12 |
13 | ## Function overview:
14 |
15 | * `vendor/bin/robo build` - Builds your extension into an installable Joomla! package or zip file including replacements
16 | * `vendor/bin/robo generate` - Generate extension skeletons
17 | * `vendor/bin/robo map` - Map (Symlink) your extension into a running Joomla! installation
18 | * `vendor/bin/robo headers` - Adds / updates the copyright headers in the source directory (set them in the jorobo.ini)
19 | * `vendor/bin/robo bump` - Exchanges the string `__DEPLOY_VERSION__` in each file in the source directory with the version number set in the jorobo.ini.
20 |
21 | ## Documentation
22 | You can find the documentation [here](docs/index.md). The following topics are covered:
23 | * [Setup Process](docs/Setup.md)
24 | * [Build Process](docs/Build.md)
25 | * [Deploy Process](docs/Deploy.md)
26 | * [Generate Process](docs/Generate.md)
27 | * [Additional Tools](docs/Misc.md)
28 |
29 | ## Copyright
30 | * (C) 2015 Open Source Matters, Inc.
31 | * Distributed under the GNU General Public License version 2 or later
32 | * See [License details](LICENSE)
33 |
--------------------------------------------------------------------------------
/RoboFile.php:
--------------------------------------------------------------------------------
1 | stopOnFail(true);
37 | }
38 |
39 | /**
40 | * Map into Joomla installation.
41 | *
42 | * @param String $target The target joomla instance
43 | *
44 | * @return void
45 | */
46 | public function map($target, $params = ['base' => JPATH_BASE])
47 | {
48 | $this->task(\Joomla\Jorobo\Tasks\Map::class, $target, $params)->run();
49 | }
50 |
51 | /**
52 | * Build the joomla extension package
53 | *
54 | * @param array $params Additional params
55 | *
56 | * @return void
57 | */
58 | public function build(ConsoleIO $io, $params = ['dev' => false, 'base' => JPATH_BASE])
59 | {
60 | $this->task(\Joomla\Jorobo\Tasks\Build::class, $params)->run();
61 | }
62 |
63 | /**
64 | * Generate an extension skeleton - not implemented yet
65 | *
66 | * @param array $extensions Extensions to build (com_xy, mod_xy, pkg_name, plg_type_name, tpl_name)
67 | *
68 | * @return void
69 | */
70 | public function generate(array $extensions, $params = ['base' => JPATH_BASE])
71 | {
72 | foreach ($extensions as $extension) {
73 | switch (substr($extension, 0, 3)) {
74 | case 'com':
75 | $this->task(\Joomla\Jorobo\Tasks\Generate\Component::class, $extension, $params)->run();
76 | break;
77 | case 'mod':
78 | $this->task(\Joomla\Jorobo\Tasks\Generate\Module::class, $extension, $params)->run();
79 | break;
80 | case 'pkg':
81 | $this->task(\Joomla\Jorobo\Tasks\Generate\Package::class, $extension, $params)->run();
82 | break;
83 | case 'plg':
84 | $this->task(\Joomla\Jorobo\Tasks\Generate\Plugin::class, $extension, $params)->run();
85 | break;
86 | case 'tpl':
87 | $this->task(\Joomla\Jorobo\Tasks\Generate\Template::class, $extension, $params)->run();
88 | break;
89 | }
90 | }
91 | }
92 |
93 | /**
94 | * Generate a component skeleton - not implemented yet
95 | *
96 | * @param string $name Component name to build (e.g. com_xy)
97 | *
98 | * @return void
99 | */
100 | public function generateComponent($name, $params = ['base' => JPATH_BASE, 'site' => true, 'api' => false, 'media' => false])
101 | {
102 | $this->task(\Joomla\Jorobo\Tasks\Generate\Component::class, $name, $params)->run();
103 | }
104 |
105 | /**
106 | * Generate a new component view skeleton - not implemented yet
107 | *
108 | * @param string $name Component name to target (e.g. com_xy)
109 | * @param string $view Name of the view (e.g. article)
110 | *
111 | * @return void
112 | */
113 | public function generateView($name, $view, $params = ['base' => JPATH_BASE])
114 | {
115 | $this->task(\Joomla\Jorobo\Tasks\Generate\Component::class, $name, $params)->run();
116 | }
117 |
118 | /**
119 | * Generate a module skeleton - not implemented yet
120 | *
121 | * The module is generated in a folder structure fitting to directly
122 | * commit to a git repository. The structure follows the best coding
123 | * examples for Joomla 4.
124 | *
125 | * @param string $name Module name to build (e.g. mod_xy)
126 | * @param array $params
127 | * @option $base A base path for the repository
128 | * @option $client Select the client to build for ('site' or 'admin')
129 | *
130 | * @return void
131 | */
132 | public function generateModule($name, $params = ['base' => JPATH_BASE, 'client' => 'site'])
133 | {
134 | $this->task(\Joomla\Jorobo\Tasks\Generate\Module::class, $name, $params)->run();
135 | }
136 |
137 | /**
138 | * Update copyright headers for this project. (Set the text up in the jorobo.ini)
139 | *
140 | * @return void
141 | */
142 | public function headers($params = ['base' => JPATH_BASE])
143 | {
144 | $this->task(\Joomla\Jorobo\Tasks\CopyrightHeader::class, $params)->run();
145 | }
146 |
147 | /**
148 | * Bump Version placeholder __DEPLOY_VERSION__ in this project. (Set the version up in the jorobo.ini)
149 | *
150 | * @return void
151 | *
152 | * @since 1.0.0
153 | */
154 | public function bump($params = ['base' => JPATH_BASE])
155 | {
156 | $this->task(\Joomla\Jorobo\Tasks\BumpVersion::class, $params)->run();
157 | }
158 |
159 | /**
160 | * Generate joomla.asset.json files
161 | *
162 | * @return void
163 | * @since __DEPLOY_VERSION__
164 | */
165 | public function assetJSON()
166 | {
167 | if (!file_exists('jorobo.ini')) {
168 | $this->_copy('jorobo.dist.ini', 'jorobo.ini');
169 | }
170 |
171 | $this->task(\Joomla\Jorobo\Tasks\AssetJSON::class)->run();
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/assets/init/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style end of lines and a blank line at the end of the file
7 | [*]
8 | indent_style = tab
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [*.php]
15 | indent_style = space
16 | indent_size = 4
17 |
18 | [*.{js,json,scss,css,yml,vue}]
19 | indent_style = space
20 | indent_size = 2
21 |
--------------------------------------------------------------------------------
/assets/init/.gitignore:
--------------------------------------------------------------------------------
1 | # Builds
2 | build
3 | releases
4 | dist
5 |
6 | # OSX
7 | .DS_Store
8 | ._*
9 | .Spotlight-V100
10 | .Trashes
11 |
12 | # Windows
13 | Thumbs.db
14 | Desktop.ini
15 |
16 | # PHPStorm
17 | .idea/
18 |
19 | # Sublime Text
20 | *.sublime*
21 |
22 | # Eclipse
23 | .buildpath
24 | .project
25 | .settings
26 |
27 | # Temp files
28 | *.tmp
29 | *.bak
30 | *.swp
31 | *~.nib
32 | *~
33 |
34 | # composer
35 | composer.phar
36 | vendor
37 |
38 | # Robo
39 | robo.phar
40 | RoboFile.ini
41 |
42 | #cypress
43 | node_modules
44 | /tests/cypress/output/screenshots
45 | !/tests/cypress/output/screenshots/.gitkeep
46 | /tests/cypress/output/videos
47 | !/tests/cypress/output/videos/.gitkeep
48 | cypress.config.js
49 | joomla
50 |
51 | # Package building related
52 | jorobo.ini
53 |
54 |
--------------------------------------------------------------------------------
/assets/init/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 |
6 | * @license GNU General Public License version 2 or later; see LICENSE.txt
7 | */
8 |
9 | /**
10 | * This is the configuration file for php-cs-fixer
11 | *
12 | * @see https://github.com/FriendsOfPHP/PHP-CS-Fixer
13 | * @see https://mlocati.github.io/php-cs-fixer-configurator/#version:3.0
14 | *
15 | *
16 | * If you would like to run the automated clean up, then open a command line and type one of the commands below
17 | *
18 | * To run a quick dry run to see the files that would be modified:
19 | *
20 | * ./component/backend/vendor/bin/php-cs-fixer fix --dry-run
21 | *
22 | * To run a full check, with automated fixing of each problem :
23 | *
24 | * ./component/backend/vendor/bin/php-cs-fixer fix
25 | *
26 | * You can run the clean up on a single file if you need to, this is faster
27 | *
28 | * ./component/backend/vendor/bin/php-cs-fixer fix --dry-run administrator/index.php
29 | * ./component/backend/vendor/bin/php-cs-fixer fix administrator/index.php
30 | */
31 |
32 | // Add all the core Joomla folders
33 | $finder = PhpCsFixer\Finder::create()
34 | ->in(
35 | [
36 | __DIR__ . '/src',
37 | ]
38 | )
39 | // Ignore template files as PHP CS fixer can't handle them properly
40 | // https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/3702#issuecomment-396717120
41 | ->notPath('/vendor/')
42 | ->notPath('/tmpl/');
43 |
44 | $config = new PhpCsFixer\Config();
45 | $config
46 | ->setRiskyAllowed(true)
47 | ->setHideProgress(false)
48 | ->setUsingCache(false)
49 | ->setRules(
50 | [
51 | // Basic ruleset is PSR 12
52 | '@PSR12' => true,
53 | // Short array syntax
54 | 'array_syntax' => ['syntax' => 'short'],
55 | // List of values separated by a comma is contained on a single line should not have a trailing comma like [$foo, $bar,] = ...
56 | 'no_trailing_comma_in_singleline' => true,
57 | // Arrays on multiline should have a trailing comma
58 | 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
59 | // Align elements in multiline array and variable declarations on new lines below each other
60 | 'binary_operator_spaces' => ['operators' => ['=>' => 'align_single_space_minimal', '=' => 'align', '??=' => 'align']],
61 | // The "No break" comment in switch statements
62 | 'no_break_comment' => ['comment_text' => 'No break'],
63 | // Remove unused imports
64 | 'no_unused_imports' => true,
65 | // Classes from the global namespace should not be imported
66 | 'global_namespace_import' => ['import_classes' => false, 'import_constants' => false, 'import_functions' => false],
67 | // Alpha order imports
68 | 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'],
69 | // There should not be useless else cases
70 | 'no_useless_else' => true,
71 | // Native function invocation
72 | 'native_function_invocation' => ['include' => ['@compiler_optimized']],
73 | // Adds null to type declarations when parameter have a default null value
74 | 'nullable_type_declaration_for_default_null_value' => true,
75 | ]
76 | )
77 | ->setFinder($finder);
78 |
79 | return $config;
80 |
--------------------------------------------------------------------------------
/assets/init/RoboFile.php:
--------------------------------------------------------------------------------
1 | false])
55 | {
56 | if (!file_exists('jorobo.ini')) {
57 | $this->_copy('jorobo.dist.ini', 'jorobo.ini');
58 | }
59 |
60 | $this->task(\Joomla\Jorobo\Tasks\Build::class, $params)->run();
61 | }
62 |
63 | /**
64 | * Update copyright headers for this project. (Set the text up in the jorobo.ini)
65 | *
66 | * @return void
67 | * @since __DEPLOY_VERSION__
68 | */
69 | public function headers()
70 | {
71 | if (!file_exists('jorobo.ini')) {
72 | $this->_copy('jorobo.dist.ini', 'jorobo.ini');
73 | }
74 |
75 | $this->task(\Joomla\Jorobo\Tasks\CopyrightHeader::class)->run();
76 | }
77 |
78 | /**
79 | * Update Version __DEPLOY_VERSION__ in Component. (Set the version up in the jorobo.ini)
80 | *
81 | * @return void
82 | * @since __DEPLOY_VERSION__
83 | */
84 | public function bump()
85 | {
86 | $this->task(\Joomla\Jorobo\Tasks\BumpVersion::class)->run();
87 | }
88 |
89 | /**
90 | * Map into Joomla installation.
91 | *
92 | * @param String $target The target joomla instance
93 | *
94 | * @return void
95 | * @since __DEPLOY_VERSION__
96 | *
97 | */
98 | public function map($target)
99 | {
100 | $this->task(\Joomla\Jorobo\Tasks\Map::class, $target)->run();
101 | }
102 |
103 | /**
104 | * Generate joomla.asset.json files
105 | *
106 | * @return void
107 | * @since __DEPLOY_VERSION__
108 | */
109 | public function assetJSON()
110 | {
111 | if (!file_exists('jorobo.ini')) {
112 | $this->_copy('jorobo.dist.ini', 'jorobo.ini');
113 | }
114 |
115 | $this->task(\Joomla\Jorobo\Tasks\AssetJSON::class)->run();
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/assets/init/jorobo.dist.ini:
--------------------------------------------------------------------------------
1 | extension = ##PROJECT##
2 | version = 1.0.0
3 | source = src
4 | target = package
5 |
6 | ; Create a pre-release of the extension on GitHub
7 | ; Add your personal GitHub access tocken
8 | ; and add Release to the target (separated by space) above
9 | [github]
10 | remote = origin
11 | branch = develop
12 | token =
13 | owner =
14 | repository =
15 | changelog_source = commits
16 |
17 | ; Automatic upload of the built extension package to an FTP server
18 | [ftp]
19 | host =
20 | port = 21
21 | user =
22 | password =
23 | ssl = false
24 | target = /
25 |
26 | ; Adds / replaces copyright headers at the beginning of files in the source folder
27 | [header]
28 | files = php,js
29 | exclude =
30 | text = "
31 | /**
32 | * @package ###PACKAGE###
33 | * @subpackage ###SUBPACKAGE###
34 | *
35 | * @copyright (C) ##YEAR## ##COMPANY## <##URL##>
36 | * @license GNU General Public License version 2 or later; see LICENSE.txt
37 | */
38 | "
39 |
--------------------------------------------------------------------------------
/assets/init/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/phpstan/phpstan-deprecation-rules/rules.neon
3 | parameters:
4 | level: 0
5 | paths:
6 | - src
7 | scanDirectories:
8 | - joomla
9 | ignoreErrors:
10 | reportUnmatchedIgnoredErrors: false
11 |
--------------------------------------------------------------------------------
/assets/init/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests/unit
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/assets/init/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The Joomla CMS PSR-12 exceptions.
4 |
5 |
6 |
7 |
8 |
9 | src/administrator/components/**/layouts/*
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | src/administrator/components/**/src/Table/*\.php
23 |
24 |
25 |
26 | src/administrator/components/**/script\.php
27 | src/administrator/manifests/packages/**/script\.php
28 |
29 |
30 |
31 | src/administrator/components/**/script\.php
32 | src/administrator/manifests/packages/**/script\.php
33 |
34 |
35 |
--------------------------------------------------------------------------------
/bin/jorobo:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | add(new \Joomla\Jorobo\Command\InitCommand());
21 |
22 | $application->run();
23 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "joomla-projects/jorobo",
3 | "description": "Tools and Tasks based on Robo.li for Joomla Extension Development and Releases",
4 | "license": "GPL-2.0-or-later",
5 | "authors": [
6 | {
7 | "name": "Yves Hoppe",
8 | "email": "yves@compojoom.com"
9 | },
10 | {
11 | "name": "Niels Nübel",
12 | "email": "niels@niels-nuebel.de"
13 | },
14 | {
15 | "name": "Niels Braczek",
16 | "email": "nbraczek@bsds.de"
17 | },
18 | {
19 | "name": "Hannes Papenberg",
20 | "email": "info@joomlager.de"
21 | },
22 | {
23 | "name": "Harald Leithner",
24 | "email": "harald.leithner@community.joomla.org"
25 | }
26 | ],
27 | "config": {
28 | "platform": {
29 | "php": "8.2"
30 | }
31 | },
32 | "autoload": {
33 | "psr-4": {
34 | "Joomla\\Jorobo\\": "src"
35 | }
36 | },
37 | "autoload-dev": {
38 | "psr-4": {
39 | "Joomla\\Jorobo\\": "src",
40 | "Robo\\": "tests/src"
41 | }
42 | },
43 | "bin": [
44 | "bin/jorobo"
45 | ],
46 | "require" : {
47 | "php": "~8.2",
48 | "ext-simplexml": "*",
49 | "consolidation/robo": "^5.1.0",
50 | "joomla/github": "~2|~3"
51 | },
52 | "require-dev": {
53 | "squizlabs/php_codesniffer": "~3.10.3",
54 | "friendsofphp/php-cs-fixer": "^3.73.1",
55 | "phan/phan": "^5.4.5",
56 | "phpunit/phpunit": "^11.5.15",
57 | "phpstan/phpstan": "^2.1.11",
58 | "phpstan/phpstan-deprecation-rules": "^2.0.1"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/jorobo.dist.ini:
--------------------------------------------------------------------------------
1 | ; Sample configuration file for JoRobo
2 | extension =
3 | version =
4 | source = src
5 | ; Deployment tasks, can contain multiple, separate with spaces
6 | target = package
7 |
8 | ; JoRobo can make releases (including package upload)
9 | ; on github for you. Add your personal access token
10 | ; and add release to the target (space separated)
11 | [github]
12 | remote = origin
13 | branch = develop
14 | token =
15 | owner = joomla-projects
16 | repository = weblinks
17 | changelog_source = commits
18 |
19 | ; Automatic upload of the built extension to an FTP server
20 | [ftp]
21 | host =
22 | port = 21
23 | user =
24 | password =
25 | ssl = false
26 | target = /
27 |
28 | ; Adds / replaces copyright headers at the beginning of files
29 | [header]
30 | files = php,js,xml
31 | exclude =
32 | text = "
33 | /**
34 | * @package JoRobo
35 | *
36 | * @copyright Copyright (C) 2005 - ##YEAR## Open Source Matters, Inc. All rights reserved.
37 | * @license GNU General Public License version 2 or later; see LICENSE.txt
38 | */
39 | "
40 |
--------------------------------------------------------------------------------
/jorobo.ini:
--------------------------------------------------------------------------------
1 | extension =
2 | version =
3 | source = src
4 | target = zip
5 |
6 | [github]
7 | remote = origin
8 | branch = develop
9 | token =
10 | owner = joomla-projects
11 | repository = jorobo
12 | changelog_source = commits
13 |
14 | [ftp]
15 | host =
16 | port = 21
17 | user =
18 | password =
19 | ssl = false
20 | target = /
21 |
22 | [header]
23 | files = php,js,xml
24 | exclude =
25 | text = "
26 | /**
27 | * @package JoRobo
28 | *
29 | * @copyright Copyright (C) 2005 - ##YEAR## Open Source Matters, Inc. All rights reserved.
30 | * @license GNU General Public License version 2 or later; see LICENSE.txt
31 | */
32 | "
33 |
--------------------------------------------------------------------------------
/src/Command/InitCommand.php:
--------------------------------------------------------------------------------
1 | setName('init')
39 | ->setDescription('Initialise a folder as a Joomla extension repository.')
40 | ;
41 | }
42 |
43 | /**
44 | * Internal function to execute the command.
45 | *
46 | * @param InputInterface $input The input to inject into the command.
47 | * @param OutputInterface $output The output to inject into the command.
48 | *
49 | * @return integer The command exit code
50 | *
51 | * @since 1.0.0
52 | */
53 | protected function execute(InputInterface $input, OutputInterface $output): int
54 | {
55 | $io = new SymfonyStyle($input, $output);
56 | $io->title('JoRobo Init');
57 | $io->info('Initialising folder for a Joomla extension');
58 | $this->io = $io;
59 |
60 | if (is_dir(JPATH_ROOT) && !is_file(JPATH_ROOT . '/composer.json')) {
61 | $io->error('The script is run from an unknown place and can\'t reliably find the root path of the repository. The discovered path was ' . JPATH_ROOT);
62 |
63 | return Command::FAILURE;
64 | }
65 |
66 | // Do we initialise with all features?
67 | $all = $io->ask('Do you want to initialise with all options? (JoRobo, gitignore, codestyle, phpstan, tests)', 'yes');
68 |
69 | if (!is_dir(JPATH_ROOT . '/src')) {
70 | $io->writeln('Creating /src folder');
71 | mkdir(JPATH_ROOT . '/src');
72 | }
73 |
74 | $io->writeln('Setting up JoRobo');
75 | $this->copy(JOROBO_ROOT . '/assets/init/RoboFile.php', JPATH_ROOT . '/RoboFile.php');
76 | $this->copy(JOROBO_ROOT . '/assets/init/jorobo.dist.ini', JPATH_ROOT . '/jorobo.dist.ini');
77 |
78 | if ($all == 'yes' || $io->ask('Want to add codestyle checks?', 'yes') === 'yes') {
79 | $io->writeln('Setting up codestyle checks');
80 | $this->copy(JOROBO_ROOT . '/assets/init/.editorconfig', JPATH_ROOT . '/.editorconfig');
81 | $this->copy(JOROBO_ROOT . '/assets/init/.php-cs-fixer.dist.php', JPATH_ROOT . '/.php-cs-dist-fixer.php');
82 | $this->copy(JOROBO_ROOT . '/assets/init/ruleset.xml', JPATH_ROOT . '/ruleset.xml');
83 |
84 | exec('cd ' . JPATH_ROOT . ' && composer require --dev squizlabs/php_codesniffer friendsofphp/php-cs-fixer');
85 | }
86 |
87 | if ($all == 'yes' || $io->ask('Want to add gitignore file?', 'yes') === 'yes') {
88 | $io->writeln('Setting up gitignore');
89 | $this->copy(JOROBO_ROOT . '/assets/init/.gitignore', JPATH_ROOT . '/.gitignore');
90 | }
91 |
92 | if ($all == 'yes' || $io->ask('Want to add phpstan static code analysis?', 'yes') === 'yes') {
93 | $io->writeln('Setting up phpstan');
94 | $this->copy(JOROBO_ROOT . '/assets/init/phpstan.neon', JPATH_ROOT . '/phpstan.neon');
95 |
96 | exec('cd ' . JPATH_ROOT . ' && composer require --dev phpstan/phpstan phpstan/phpstan-deprecation-rules');
97 | }
98 |
99 | if ($all == 'yes' || $io->ask('Want to add phpunit tests?', 'yes') === 'yes') {
100 | $io->writeln('Setting up phpunit');
101 | $this->copy(JOROBO_ROOT . '/assets/init/phpunit.xml.dist', JPATH_ROOT . '/phpunit.xml.dist');
102 |
103 | exec('cd ' . JPATH_ROOT . ' && composer require --dev phpunit/phpunit');
104 | }
105 |
106 | return Command::SUCCESS;
107 | }
108 |
109 | /**
110 | * Helper function to cleanup paths before copying files
111 | *
112 | * @param string $src Source file to copy from
113 | * @param string $dst Destination file to copy to
114 | *
115 | * @return bool
116 | */
117 | private function copy($src, $dst)
118 | {
119 | if ('\\' === \DIRECTORY_SEPARATOR) {
120 | $src = strtr($src, '/', '\\');
121 | $dst = strtr($dst, '/', '\\');
122 | }
123 |
124 | if (is_file($dst)) {
125 | $this->io->note('File already exists: ' . $dst);
126 |
127 | return false;
128 | }
129 |
130 | return copy($src, $dst);
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Tasks/AssetJSON.php:
--------------------------------------------------------------------------------
1 | printTaskInfo("Generating joomla.asset.json for webasset manager");
38 |
39 | $folders = glob($this->getSourceFolder() . '/media/*', GLOB_ONLYDIR);
40 |
41 | foreach ($folders as $folder) {
42 | $extension = basename($folder);
43 |
44 | if (file_exists($folder . '/joomla.asset.json')) {
45 | $this->printTaskInfo('Updating joomla.asset.json for ' . $extension);
46 | $assetFile = json_decode(file_get_contents($folder . '/joomla.asset.json'));
47 | } else {
48 | $this->printTaskInfo('Generating joomla.asset.json for ' . $extension);
49 |
50 | $assetFile = new \stdClass();
51 | $assetFile->{'$schema'} = 'https://developer.joomla.org/schemas/json-schema/web_assets.json';
52 | $assetFile->name = $extension;
53 | $assetFile->version = $this->getJConfig()->version;
54 | $assetFile->description = '';
55 | $assetFile->license = 'GPL-2.0-or-later';
56 | $assetFile->assets = [];
57 | }
58 |
59 | foreach (['js', 'css'] as $type) {
60 | if (is_dir($folder . '/' . $type)) {
61 | $files = glob($folder . '/' . $type . '/*.' . $type);
62 |
63 | foreach ($files as $file) {
64 | if (str_ends_with($file, '.min.' . $type) && file_exists(str_replace('.min.' . $type, '.' . $type, $file))) {
65 | continue;
66 | }
67 |
68 | $name = str_replace(['.min.' . $type, '.' . $type], '', basename($file));
69 |
70 | $found = false;
71 | foreach ($assetFile->assets as $asset) {
72 | if ($asset->type == ($type == 'js' ? 'script' : 'style') && $asset->name == $extension . '.' . $name) {
73 | $found = true;
74 | break;
75 | }
76 | }
77 |
78 | if (!$found) {
79 | $entry = new \stdClass();
80 | $entry->name = $extension . '.' . $name;
81 | $entry->type = $type == 'js' ? 'script' : 'style';
82 | $uri = $extension . '/' . basename($file);
83 |
84 | if (!str_ends_with($entry->name, '.min.' . $type) && file_exists(substr($file, 0, -(strlen($type) + 1)) . '.min.' . $type)) {
85 | $uri = $extension . '/' . substr(basename($file), 0, -(strlen($type) + 1)) . '.min.' . $type;
86 | }
87 |
88 | $entry->uri = $uri;
89 |
90 | if ($type == 'js') {
91 | $entry->dependencies = [];
92 | $entry->attributes = (object)['type' => 'module'];
93 | }
94 |
95 | $assetFile->assets[] = $entry;
96 | }
97 | }
98 | }
99 | }
100 |
101 | file_put_contents($folder . '/joomla.asset.json', json_encode($assetFile, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
102 | }
103 |
104 | $this->printTaskSuccess('Finished!!');
105 |
106 | return Result::success($this);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/Tasks/Build.php:
--------------------------------------------------------------------------------
1 | printTaskInfo('Building ' . $this->getJConfig()->extension . " " . $this->getJConfig()->version);
38 |
39 | if (!$this->checkFolders()) {
40 | return Result::error($this, 'checkFolders failed');
41 | }
42 |
43 | // Create directory
44 | $this->prepareDistDirectory();
45 |
46 | // Build extension
47 | $this->buildExtension($this->params)
48 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_DEBUG)
49 | ->run();
50 |
51 | // Create symlink to current folder
52 | if ($this->isWindows()) {
53 | if (is_dir($this->params['base'] . "\dist\current")) {
54 | rmdir($this->params['base'] . "\dist\current");
55 | }
56 | $this->taskExec('mklink /J "' . $this->params['base'] . '\dist\current" "' . $this->getWindowsPath($this->getBuildFolder()) . '"')
57 | ->run();
58 | } else {
59 | if (is_dir($this->params['base'] . "/dist/current")) {
60 | unlink($this->params['base'] . "/dist/current");
61 | }
62 | $this->taskFilesystemStack()
63 | ->symlink($this->getBuildFolder(), $this->params['base'] . "/dist/current")
64 | ->run();
65 | }
66 |
67 | // Support multiple deployment methods, separated by spaces
68 | $deploy = explode(" ", $this->getJConfig()->target);
69 |
70 | if (count($deploy)) {
71 | foreach ($deploy as $d) {
72 | $task = 'deploy' . ucfirst($d);
73 |
74 | $this->{$task}($this->params)->run();
75 | }
76 | }
77 |
78 | return Result::success($this, 'Build successful');
79 | }
80 |
81 | /**
82 | * Cleanup the given directory
83 | *
84 | * @param string $dir The dir
85 | *
86 | * @return void
87 | *
88 | * @since 1.0
89 | */
90 | private function cleanup($dir)
91 | {
92 | // Clean building directory
93 | $this->_cleanDir($dir);
94 | }
95 |
96 | /**
97 | * Prepare the directories
98 | *
99 | * @return void
100 | *
101 | * @since 1.0
102 | */
103 | private function prepareDistDirectory()
104 | {
105 | $build = $this->getBuildFolder();
106 |
107 | if (!file_exists($build)) {
108 | $this->_mkdir($build);
109 | }
110 |
111 | $this->cleanup($build);
112 | }
113 |
114 | /**
115 | * Check if local OS is Windows
116 | *
117 | * @return boolean
118 | *
119 | * @since 3.7.3
120 | */
121 | private function isWindows()
122 | {
123 | return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
124 | }
125 |
126 | /**
127 | * Return the correct path for Windows (needed by CMD)
128 | *
129 | * @param string $path Linux path
130 | *
131 | * @return string
132 | *
133 | * @since 3.7.3
134 | */
135 | private function getWindowsPath($path)
136 | {
137 | return str_replace('/', DIRECTORY_SEPARATOR, $path);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Base.php:
--------------------------------------------------------------------------------
1 | $method($fileArray);
119 | } else {
120 | $this->printTaskError('Missing method: ' . $method);
121 | }
122 |
123 | return true;
124 | }
125 |
126 | /**
127 | * Retrieve the files
128 | *
129 | * @param string $type Type (media, component etc.)
130 | *
131 | * @return mixed
132 | *
133 | * @since 1.0
134 | */
135 | public function getFiles($type)
136 | {
137 | $f = $type . 'Files';
138 |
139 | if (property_exists($this, $f)) {
140 | return self::${$f};
141 | }
142 |
143 | $this->printTaskError('Missing Files: ' . $type);
144 |
145 | return "";
146 | }
147 |
148 | /**
149 | * Adds Files / Folders to media array
150 | *
151 | * @param array $fileArray Array of files / folders
152 | *
153 | * @return void
154 | *
155 | * @since 1.0
156 | */
157 | public function addMediaFiles($fileArray)
158 | {
159 | self::$mediaFiles = array_merge(self::$mediaFiles, $fileArray);
160 | }
161 |
162 | /**
163 | * Adds Files / Folders to media array
164 | *
165 | * @param array $fileArray Array of files / folders
166 | *
167 | * @return void
168 | *
169 | * @since 1.0
170 | */
171 | public function addFrontendFiles($fileArray)
172 | {
173 | self::$frontendFiles = array_merge(self::$frontendFiles, $fileArray);
174 | }
175 |
176 | /**
177 | * Adds Files / Folders to media array
178 | *
179 | * @param array $fileArray Array of files / folders
180 | *
181 | * @return void
182 | *
183 | * @since 1.0
184 | */
185 | public function addApiFiles($fileArray)
186 | {
187 | self::$apiFiles = array_merge(self::$apiFiles, $fileArray);
188 | }
189 |
190 | /**
191 | * Adds Files / Folders to media array
192 | *
193 | * @param array $fileArray Array of files / folders
194 | *
195 | * @return void
196 | *
197 | * @since 1.0
198 | */
199 | public function addBackendFiles($fileArray)
200 | {
201 | self::$backendFiles = array_merge(self::$backendFiles, $fileArray);
202 | }
203 |
204 | /**
205 | * Adds Files / Folders to language array
206 | *
207 | * @param array $fileArray Array of files / folders
208 | *
209 | * @return void
210 | *
211 | * @since 1.0
212 | */
213 | public function addFrontendLanguageFiles($fileArray)
214 | {
215 | self::$frontendLanguageFiles = array_merge(self::$frontendLanguageFiles, $fileArray);
216 | }
217 |
218 | /**
219 | * Adds Files / Folders to language array
220 | *
221 | * @param array $fileArray Array of files / folders
222 | *
223 | * @return void
224 | *
225 | * @since 1.0
226 | */
227 | public function addBackendLanguageFiles($fileArray)
228 | {
229 | self::$backendLanguageFiles = array_merge(self::$backendLanguageFiles, $fileArray);
230 | }
231 |
232 | /**
233 | * Copies the files and maps them into an array
234 | *
235 | * @param string $path Folder path
236 | * @param string $tar Target path
237 | *
238 | * @return array
239 | *
240 | * @since 1.0
241 | */
242 | protected function copyTarget($path, $tar)
243 | {
244 | $map = [];
245 | $hdl = opendir($path);
246 |
247 | while ($entry = readdir($hdl)) {
248 | $p = $path . "/" . $entry;
249 |
250 | // Ignore hidden files
251 | if (substr($entry, 0, 1) != '.') {
252 | if (
253 | isset($this->getJConfig()->exclude)
254 | && in_array($entry, explode(',', $this->getJConfig()->exclude))
255 | ) {
256 | continue;
257 | }
258 |
259 | if (is_file($p)) {
260 | $map[] = ["file" => $entry];
261 | $this->taskFilesystemStack()
262 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
263 | ->copy($p, $tar . "/" . $entry)
264 | ->run();
265 | } else {
266 | $map[] = ["folder" => $entry];
267 | $this->taskCopyDir([$p => $tar . "/" . $entry])
268 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
269 | ->run();
270 | }
271 | }
272 | }
273 |
274 | closedir($hdl);
275 |
276 | return $map;
277 | }
278 |
279 | /**
280 | * Get the result files
281 | *
282 | * @return array
283 | *
284 | * @since 1.0
285 | */
286 | public function getResultFiles()
287 | {
288 | return $this->resultFiles;
289 | }
290 |
291 | /**
292 | * Set the result files
293 | *
294 | * @param array $resultFiles The result of the copying
295 | *
296 | * @return void
297 | *
298 | * @since 1.0
299 | */
300 | public function setResultFiles($resultFiles)
301 | {
302 | $this->resultFiles = $resultFiles;
303 | }
304 |
305 | /**
306 | * Get the current date (formatted for building)
307 | *
308 | * @return string
309 | *
310 | * @since 1.0
311 | */
312 | public function getDate()
313 | {
314 | return date('Y-m-d');
315 | }
316 |
317 | /**
318 | * Generate a list of files
319 | *
320 | * @param array $files Files and Folders array
321 | *
322 | * @return string
323 | *
324 | * @since 1.0
325 | */
326 | public function generateFileList($files)
327 | {
328 | if (!count($files)) {
329 | return "";
330 | }
331 |
332 | $text = [];
333 |
334 | foreach ($files as $f) {
335 | foreach ($f as $type => $value) {
336 | $text[] = "<" . $type . ">" . $value . "" . $type . ">";
337 | }
338 | }
339 |
340 | return implode("\n", $text);
341 | }
342 |
343 | /**
344 | * Generate a list of files
345 | *
346 | * @param array $files Files and Folders array
347 | *
348 | * @return string
349 | *
350 | * @since 1.0
351 | */
352 | public function generateLanguageFileList($files)
353 | {
354 | if (!count($files)) {
355 | return "";
356 | }
357 |
358 | $text = [];
359 |
360 | foreach ($files as $f) {
361 | foreach ($f as $tag => $value) {
362 | $text[] = '' . $tag . "/" . $value . "";
363 | }
364 | }
365 |
366 | return implode("\n", $text);
367 | }
368 |
369 | /**
370 | * Generate a list of files for plugins
371 | *
372 | * @param array $files Files and Folders array
373 | * @param string $plugin The plugin file
374 | *
375 | * @return string
376 | *
377 | * @since 1.0
378 | */
379 | public function generatePluginFileList($files, $plugin)
380 | {
381 | if (!count($files)) {
382 | return "";
383 | }
384 |
385 | $text = [];
386 |
387 | foreach ($files as $f) {
388 | foreach ($f as $type => $value) {
389 | $p = "";
390 |
391 | if (
392 | $value == $plugin . ".php"
393 | || $value == "services"
394 | ) {
395 | $p = ' plugin="' . $plugin . '"';
396 | }
397 |
398 | $text[] = "<" . $type . $p . ">" . $value . "" . $type . ">";
399 | }
400 | }
401 |
402 | return implode("\n", $text);
403 | }
404 |
405 | /**
406 | * Generate a list of files for modules
407 | *
408 | * @param array $files Files and Folders array
409 | * @param string $module The module
410 | *
411 | * @return string
412 | *
413 | * @since 1.0
414 | */
415 | public function generateModuleFileList($files, $module)
416 | {
417 | if (!count($files)) {
418 | return "";
419 | }
420 |
421 | $text = [];
422 |
423 | foreach ($files as $f) {
424 | foreach ($f as $type => $value) {
425 | $p = "";
426 |
427 | if ($value == $module . ".php") {
428 | $p = ' module="' . $module . '"';
429 | }
430 |
431 | $text[] = "<" . $type . $p . ">" . $value . "" . $type . ">";
432 | }
433 | }
434 |
435 | return implode("\n", $text);
436 | }
437 |
438 | /**
439 | * Reset the files list, before build another part
440 | *
441 | * @return void
442 | *
443 | * @since 1.0
444 | */
445 | public function resetFiles()
446 | {
447 | self::$backendFiles = [];
448 | self::$backendLanguageFiles = [];
449 | self::$frontendFiles = [];
450 | self::$frontendLanguageFiles = [];
451 | self::$mediaFiles = [];
452 | }
453 |
454 | /**
455 | * Replace Basic placeholders in file (Date, year, version)
456 | *
457 | * @param string $file Path to file
458 | *
459 | * @return void
460 | *
461 | * @since 1.0
462 | */
463 | protected function replaceInFile($file)
464 | {
465 | if (!file_exists($file)) {
466 | return;
467 | }
468 |
469 | $this->taskReplaceInFile($file)
470 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
471 | ->from(['##DATE##', '##YEAR##', '##VERSION##'])
472 | ->to([$this->getDate(), date('Y'), $this->getJConfig()->version])
473 | ->run();
474 | }
475 | }
476 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Component.php:
--------------------------------------------------------------------------------
1 | new component
54 | $this->resetFiles();
55 | $this->setExtensionName($name);
56 |
57 | $this->adminPath = $this->getSourceFolder() . "/administrator/components/com_" . $this->getExtensionName();
58 | $this->apiPath = $this->getSourceFolder() . "/api/components/com_" . $this->getExtensionName();
59 | $this->frontPath = $this->getSourceFolder() . "/components/com_" . $this->getExtensionName();
60 | }
61 |
62 | /**
63 | * Build the package
64 | *
65 | * @return Result
66 | *
67 | * @since 1.0
68 | */
69 | public function run()
70 | {
71 | $this->printTaskInfo('Building com_' . $this->getExtensionName() . ' component');
72 |
73 | // Analyze extension structure
74 | $this->analyze();
75 |
76 | // Prepare directories
77 | $this->prepareDirectories();
78 |
79 | if ($this->hasAdmin) {
80 | $this->logger->log(LogLevel::INFO, 'Copy admin files', $this->getTaskContext());
81 | $adminFiles = $this->copyTarget($this->adminPath, $this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName());
82 |
83 | $this->addFiles('backend', $adminFiles);
84 | }
85 |
86 | if ($this->hasApi) {
87 | $this->logger->log(LogLevel::INFO, 'Copy API files', $this->getTaskContext());
88 | $apiFiles = $this->copyTarget($this->apiPath, $this->getBuildFolder() . "/api/components/com_" . $this->getExtensionName());
89 |
90 | $this->addFiles('api', $apiFiles);
91 | }
92 |
93 | if ($this->hasFront) {
94 | $this->logger->log(LogLevel::INFO, 'Copy frontend files', $this->getTaskContext());
95 | $frontendFiles = $this->copyTarget($this->frontPath, $this->getBuildFolder() . "/components/com_" . $this->getExtensionName());
96 |
97 | $this->addFiles('frontend', $frontendFiles);
98 | }
99 |
100 | // Build media (relative path)
101 | if ($this->hasMedia) {
102 | $this->logger->log(LogLevel::INFO, 'Copy media files', $this->getTaskContext());
103 | $media = $this->buildMedia("media/com_" . $this->getExtensionName(), 'com_' . $this->getExtensionName(), $this->params);
104 | $media->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
105 | ->run();
106 |
107 | $this->addFiles('media', $media->getResultFiles());
108 | }
109 |
110 | // Build language files for the component
111 | if (is_dir($this->getSourceFolder() . '/administrator/language')) {
112 | $language = $this->buildLanguage("com_" . $this->getExtensionName(), $this->params)
113 | ->setVerbosityThreshold(self::VERBOSITY_VERBOSE);
114 | $language->run();
115 | }
116 |
117 | // Update XML and script.php
118 | $this->createInstaller();
119 |
120 | // Copy XML and script.php to root
121 | $this->logger->log(LogLevel::INFO, 'Copy manifest and (optional) script file', $this->getTaskContext());
122 | $adminFolder = $this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName();
123 | $xmlFile = $adminFolder . "/" . $this->getExtensionName() . ".xml";
124 | $scriptFile = $adminFolder . "/script.php";
125 |
126 | $this->taskFilesystemStack()
127 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
128 | ->copy($xmlFile, $this->getBuildFolder() . "/" . $this->getExtensionName() . ".xml")
129 | ->run();
130 |
131 | if (file_exists($scriptFile)) {
132 | $this->taskFilesystemStack()
133 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
134 | ->copy($scriptFile, $this->getBuildFolder() . "/script.php")
135 | ->run();
136 | }
137 |
138 | // Copy Readme
139 | if (is_file($this->params['base'] . "/docs/README.md")) {
140 | $this->logger->log(LogLevel::INFO, 'Copy README from /docs folder', $this->getTaskContext());
141 | $this->taskFilesystemStack()
142 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
143 | ->copy($this->params['base'] . "/docs/README.md", $this->getBuildFolder() . "/README")
144 | ->run();
145 | }
146 |
147 | $this->printTaskSuccess('Finished building com_' . $this->getExtensionName() . ' component');
148 |
149 | return Result::success($this, "Component build");
150 | }
151 |
152 | /**
153 | * Analyze the component structure
154 | *
155 | * @return void
156 | *
157 | * @since 1.0
158 | */
159 | private function analyze()
160 | {
161 | if (!file_exists($this->adminPath)) {
162 | $this->hasAdmin = false;
163 | }
164 |
165 | if (!file_exists($this->apiPath)) {
166 | $this->hasApi = false;
167 | }
168 |
169 | if (!file_exists($this->frontPath)) {
170 | $this->hasFront = false;
171 | }
172 |
173 | if (file_exists($this->getSourceFolder() . "/media/com_" . $this->getExtensionName())) {
174 | $this->hasMedia = true;
175 | }
176 | }
177 |
178 | /**
179 | * Prepare the directory structure
180 | *
181 | * @return void
182 | *
183 | * @since 1.0
184 | */
185 | private function prepareDirectories()
186 | {
187 | if ($this->hasAdmin) {
188 | $this->taskFilesystemStack()
189 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
190 | ->mkdir($this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName())
191 | ->run();
192 | }
193 |
194 | if ($this->hasApi) {
195 | $this->taskFilesystemStack()
196 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
197 | ->mkdir($this->getBuildFolder() . "/api/components/com_" . $this->getExtensionName())
198 | ->run();
199 | }
200 |
201 | if ($this->hasFront) {
202 | $this->taskFilesystemStack()
203 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
204 | ->mkdir($this->getBuildFolder() . "/components/com_" . $this->getExtensionName())
205 | ->run();
206 | }
207 | }
208 |
209 | /**
210 | * Generate the installer xml file for the component
211 | *
212 | * @return void
213 | *
214 | * @since 1.0
215 | */
216 | private function createInstaller()
217 | {
218 | $this->printTaskInfo('Creating component installer');
219 |
220 | $adminFolder = $this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName();
221 | $xmlFile = $adminFolder . "/" . $this->getExtensionName() . ".xml";
222 | $configFile = $adminFolder . "/config.xml";
223 | $scriptFile = $adminFolder . "/script.php";
224 |
225 | // Version & Date Replace
226 | $this->replaceInFile($xmlFile);
227 | $this->replaceInFile($scriptFile);
228 | $this->replaceInFile($configFile);
229 |
230 | // Files and folders
231 | if ($this->hasAdmin) {
232 | $f = $this->generateFileList($this->getFiles('backend'));
233 |
234 | $this->taskReplaceInFile($xmlFile)
235 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
236 | ->from('##BACKEND_COMPONENT_FILES##')
237 | ->to($f)
238 | ->run();
239 |
240 | // Language files
241 | $f = $this->generateLanguageFileList($this->getFiles('backendLanguage'));
242 |
243 | $this->taskReplaceInFile($xmlFile)
244 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
245 | ->from('##BACKEND_LANGUAGE_FILES##')
246 | ->to($f)
247 | ->run();
248 | }
249 |
250 | if ($this->hasApi) {
251 | $f = $this->generateFileList($this->getFiles('api'));
252 |
253 | $this->taskReplaceInFile($xmlFile)
254 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
255 | ->from('##API_COMPONENT_FILES##')
256 | ->to($f)
257 | ->run();
258 | }
259 |
260 | if ($this->hasFront) {
261 | $f = $this->generateFileList($this->getFiles('frontend'));
262 |
263 | $this->taskReplaceInFile($xmlFile)
264 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
265 | ->from('##FRONTEND_COMPONENT_FILES##')
266 | ->to($f)
267 | ->run();
268 |
269 | // Language files
270 | $f = $this->generateLanguageFileList($this->getFiles('frontendLanguage'));
271 |
272 | $this->taskReplaceInFile($xmlFile)
273 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
274 | ->from('##FRONTEND_LANGUAGE_FILES##')
275 | ->to($f)
276 | ->run();
277 | }
278 |
279 | // Media files
280 | if ($this->hasMedia) {
281 | $f = $this->generateFileList($this->getFiles('media'));
282 |
283 | $this->taskReplaceInFile($xmlFile)
284 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
285 | ->from('##MEDIA_FILES##')
286 | ->to($f)
287 | ->run();
288 | }
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Extension.php:
--------------------------------------------------------------------------------
1 | printTaskInfo('Building ' . $this->getJConfig()->extension . ' extension package');
66 |
67 | $this->analyze();
68 |
69 | // Build component
70 | if ($this->hasComponents) {
71 | $path = $this->getSourceFolder() . "/administrator/components";
72 |
73 | // Get every component
74 | $dir = new \DirectoryIterator($path);
75 |
76 | foreach ($dir as $component) {
77 | if (
78 | $component->isDot()
79 | || substr($component->getFilename(), 0, 4) != 'com_'
80 | || !is_dir($path . '/' . $component->getFilename())
81 | ) {
82 | continue;
83 | }
84 |
85 | $this->buildComponent(substr($component->getFilename(), 4), $this->params)->run();
86 | }
87 | }
88 |
89 | // Frontend Modules
90 | if ($this->hasModules) {
91 | $path = $this->getSourceFolder() . "/modules";
92 |
93 | // Get every module
94 | $dir = new \DirectoryIterator($path);
95 |
96 | foreach ($dir as $module) {
97 | if (
98 | $module->isDot()
99 | || substr($module->getFilename(), 0, 4) != 'mod_'
100 | || !is_dir($path . '/' . $module->getFilename())
101 | ) {
102 | continue;
103 | }
104 |
105 | $this->modules[] = $module->getFilename();
106 | $this->buildModule($module->getFilename(), $this->params)->run();
107 | }
108 | }
109 |
110 | // Backend Modules
111 | if ($this->hasAdminModules) {
112 | $path = $this->getSourceFolder() . "/administrator/modules";
113 | $params = $this->params;
114 | $params['basepath'] = $path;
115 |
116 | // Get every module
117 | $dir = new \DirectoryIterator($path);
118 |
119 | foreach ($dir as $module) {
120 | if (
121 | $module->isDot()
122 | || substr($module->getFilename(), 0, 4) != 'mod_'
123 | || !is_dir($path . '/' . $module->getFilename())
124 | ) {
125 | continue;
126 | }
127 |
128 | $this->adminModules[] = $module->getFilename();
129 | $this->buildModule($module->getFilename(), $params)->run();
130 | }
131 | }
132 |
133 | // Plugins
134 | if ($this->hasPlugins) {
135 | $path = $this->getSourceFolder() . "/plugins";
136 |
137 | // Get every plugin
138 | $hdl = opendir($path);
139 |
140 | while ($entry = readdir($hdl)) {
141 | // Only folders
142 | $p = $path . "/" . $entry;
143 |
144 | if (substr($entry, 0, 1) == '.') {
145 | continue;
146 | }
147 |
148 | if (!is_file($p)) {
149 | // Plugin type folder
150 | $type = $entry;
151 |
152 | $hdl2 = opendir($p);
153 |
154 | while ($plugin = readdir($hdl2)) {
155 | // Only folders
156 | $p2 = $path . "/" . $entry;
157 |
158 | if (substr($plugin, 0, 1) == '.') {
159 | continue;
160 | }
161 |
162 | if (!is_file($p2)) {
163 | $this->plugins[] = "plg_" . $type . "_" . $plugin;
164 | $this->buildPlugin($type, $plugin, $this->params)->run();
165 | }
166 | }
167 |
168 | closedir($hdl2);
169 | }
170 | }
171 |
172 | closedir($hdl);
173 | }
174 |
175 | if ($this->hasLibraries) {
176 | $path = $this->getSourceFolder() . "/libraries";
177 |
178 | // Get every library
179 | $hdl = opendir($path);
180 |
181 | while ($entry = readdir($hdl)) {
182 | // Only folders
183 | $p = $path . "/" . $entry;
184 |
185 | if (substr($entry, 0, 1) == '.') {
186 | continue;
187 | }
188 |
189 | if (!is_file($p)) {
190 | // Library folder
191 | $this->libraries[] = $entry;
192 | $this->buildLibrary($entry, $this->params, $this->hasComponents)->run();
193 | }
194 | }
195 |
196 | closedir($hdl);
197 | }
198 |
199 | // Templates
200 | if ($this->hasTemplates) {
201 | $path = $this->getSourceFolder() . "/templates";
202 |
203 | // Get every module
204 | $hdl = opendir($path);
205 |
206 | while ($entry = readdir($hdl)) {
207 | // Only folders
208 | $p = $path . "/" . $entry;
209 |
210 | if (substr($entry, 0, 1) == '.') {
211 | continue;
212 | }
213 |
214 | if (!is_file($p)) {
215 | // Template folder
216 | $this->templates[] = $entry;
217 | $this->buildTemplate($entry, $this->params)->run();
218 | }
219 | }
220 |
221 | closedir($hdl);
222 | }
223 |
224 | // Build file
225 | if ($this->hasFile) {
226 | $this->buildFile($this->params)->run();
227 | }
228 |
229 | // Build component
230 | if ($this->hasPackage) {
231 | $this->buildPackage($this->params)->run();
232 | }
233 |
234 | // Replacements (date, version etc.) in every php file
235 | $this->printTaskInfo('Doing replacements in files');
236 | $dir = new \RecursiveDirectoryIterator($this->getBuildFolder(), \RecursiveDirectoryIterator::SKIP_DOTS);
237 | $it = new \RecursiveIteratorIterator($dir);
238 |
239 | foreach ($it as $file) {
240 | if (in_array(pathinfo($file, PATHINFO_EXTENSION), ['php', 'js'])) {
241 | $this->replaceInFile($file);
242 | }
243 | }
244 |
245 | $this->printTaskSuccess('Finished Building ' . $this->getJConfig()->extension . ' extension package');
246 |
247 | return Result::success($this);
248 | }
249 |
250 | /**
251 | * Analyze the extension structure
252 | *
253 | * @return void
254 | *
255 | * @since 1.0
256 | */
257 | private function analyze()
258 | {
259 | // Check if we have component, module, plugin etc.
260 | $folders = glob($this->getSourceFolder() . "/administrator/components/com_*", GLOB_ONLYDIR);
261 |
262 | if (count($folders) === 0) {
263 | $this->hasComponents = false;
264 | }
265 |
266 | if (file_exists($this->getSourceFolder() . "/administrator/modules")) {
267 | $this->hasModules = true;
268 | }
269 |
270 | if (!file_exists($this->getSourceFolder() . "/modules")) {
271 | $this->hasModules = false;
272 | }
273 |
274 | if (!file_exists($this->getSourceFolder() . "/plugins")) {
275 | $this->hasPlugins = false;
276 | }
277 |
278 | if (!file_exists($this->getSourceFolder() . "/templates")) {
279 | $this->hasTemplates = false;
280 | }
281 |
282 | if (!file_exists($this->getSourceFolder() . "/libraries")) {
283 | $this->hasLibraries = false;
284 | }
285 |
286 | if (!file_exists($this->getSourceFolder() . "/administrator/manifests/files")) {
287 | $this->hasFile = false;
288 | }
289 |
290 | if (!file_exists($this->getSourceFolder() . "/administrator/manifests/packages")) {
291 | $this->hasPackage = false;
292 | }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/src/Tasks/Build/File.php:
--------------------------------------------------------------------------------
1 | new component
51 | $this->resetFiles();
52 |
53 | $this->adminPath = $this->getSourceFolder() . "/administrator/components/com_" . $this->getExtensionName();
54 | $this->apiPath = $this->getSourceFolder() . "/api/components/com_" . $this->getExtensionName();
55 | $this->frontPath = $this->getSourceFolder() . "/components/com_" . $this->getExtensionName();
56 | }
57 |
58 | /**
59 | * Build the package
60 | *
61 | * @return Result
62 | *
63 | * @since 1.0
64 | */
65 | public function run()
66 | {
67 | $this->say('Building component');
68 |
69 | // Analyze extension structure
70 | $this->analyze();
71 |
72 | // Prepare directories
73 | $this->prepareDirectories();
74 |
75 | if ($this->hasAdmin) {
76 | $adminFiles = $this->copyTarget($this->adminPath, $this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName());
77 |
78 | $this->addFiles('backend', $adminFiles);
79 | }
80 |
81 | if ($this->hasApi) {
82 | $apiFiles = $this->copyTarget($this->apiPath, $this->getBuildFolder() . "/api/components/com_" . $this->getExtensionName());
83 |
84 | $this->addFiles('api', $apiFiles);
85 | }
86 |
87 | if ($this->hasFront) {
88 | $frontendFiles = $this->copyTarget($this->frontPath, $this->getBuildFolder() . "/components/com_" . $this->getExtensionName());
89 |
90 | $this->addFiles('frontend', $frontendFiles);
91 | }
92 |
93 | // Build media (relative path)
94 | $media = $this->buildMedia("media/com_" . $this->getExtensionName(), 'com_' . $this->getExtensionName());
95 | $media->run();
96 |
97 | $this->addFiles('media', $media->getResultFiles());
98 |
99 | // Build language files for the component
100 | $language = $this->buildLanguage("com_" . $this->getExtensionName());
101 | $language->run();
102 |
103 | // Update XML and script.php
104 | $this->createInstaller();
105 |
106 | // Copy XML and script.php to root
107 | $adminFolder = $this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName();
108 | $xmlFile = $adminFolder . "/" . $this->getExtensionName() . ".xml";
109 | $scriptFile = $adminFolder . "/script.php";
110 |
111 | $this->_copy($xmlFile, $this->getBuildFolder() . "/" . $this->getExtensionName() . ".xml");
112 |
113 | if (file_exists($scriptFile)) {
114 | $this->_copy($scriptFile, $this->getBuildFolder() . "/script.php");
115 | }
116 |
117 | // Copy Readme
118 | if (is_file($this->params['base'] . "/docs/README.md")) {
119 | $this->_copy($this->params['base'] . "/docs/README.md", $this->getBuildFolder() . "/README");
120 | }
121 |
122 | return Result::success($this, "Component build");
123 | }
124 |
125 | /**
126 | * Analyze the component structure
127 | *
128 | * @return void
129 | *
130 | * @since 1.0
131 | */
132 | private function analyze()
133 | {
134 | if (!file_exists($this->adminPath)) {
135 | $this->hasAdmin = false;
136 | }
137 |
138 | if (!file_exists($this->apiPath)) {
139 | $this->hasApi = false;
140 | }
141 |
142 | if (!file_exists($this->frontPath)) {
143 | $this->hasFront = false;
144 | }
145 |
146 | if (file_exists($this->sourceFolder . "/media")) {
147 | $this->hasMedia = true;
148 | }
149 | }
150 |
151 | /**
152 | * Prepare the directory structure
153 | *
154 | * @return void
155 | *
156 | * @since 1.0
157 | */
158 | private function prepareDirectories()
159 | {
160 | if ($this->hasAdmin) {
161 | $this->_mkdir($this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName());
162 | }
163 |
164 | if ($this->hasApi) {
165 | $this->_mkdir($this->getBuildFolder() . "/api/components/com_" . $this->getExtensionName());
166 | }
167 |
168 | if ($this->hasFront) {
169 | $this->_mkdir($this->getBuildFolder() . "/components/com_" . $this->getExtensionName());
170 | }
171 | }
172 |
173 | /**
174 | * Generate the installer xml file for the component
175 | *
176 | * @return void
177 | *
178 | * @since 1.0
179 | */
180 | private function createInstaller()
181 | {
182 | $this->printTaskInfo("Creating file installer");
183 |
184 | $adminFolder = $this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName();
185 | $xmlFile = $adminFolder . "/" . $this->getExtensionName() . ".xml";
186 | $configFile = $adminFolder . "/config.xml";
187 | $scriptFile = $adminFolder . "/script.php";
188 | $helperFile = $adminFolder . "/helpers/defines.php";
189 |
190 | // Version & Date Replace
191 | $this->replaceInFile($xmlFile);
192 | $this->replaceInFile($scriptFile);
193 | $this->replaceInFile($configFile);
194 | $this->replaceInFile($helperFile);
195 |
196 | // Files and folders
197 | if ($this->hasAdmin) {
198 | $f = $this->generateFileList($this->getFiles('backend'));
199 |
200 | $this->taskReplaceInFile($xmlFile)
201 | ->from('##BACKEND_COMPONENT_FILES##')
202 | ->to($f)
203 | ->run();
204 |
205 | // Language files
206 | $f = $this->generateLanguageFileList($this->getFiles('backendLanguage'));
207 |
208 | $this->taskReplaceInFile($xmlFile)
209 | ->from('##BACKEND_LANGUAGE_FILES##')
210 | ->to($f)
211 | ->run();
212 | }
213 |
214 | if ($this->hasApi) {
215 | $f = $this->generateFileList($this->getFiles('api'));
216 |
217 | $this->taskReplaceInFile($xmlFile)
218 | ->from('##API_COMPONENT_FILES##')
219 | ->to($f)
220 | ->run();
221 | }
222 |
223 | if ($this->hasFront) {
224 | $f = $this->generateFileList($this->getFiles('frontend'));
225 |
226 | $this->taskReplaceInFile($xmlFile)
227 | ->from('##FRONTEND_COMPONENT_FILES##')
228 | ->to($f)
229 | ->run();
230 |
231 | // Language files
232 | $f = $this->generateLanguageFileList($this->getFiles('frontendLanguage'));
233 |
234 | $this->taskReplaceInFile($xmlFile)
235 | ->from('##FRONTEND_LANGUAGE_FILES##')
236 | ->to($f)
237 | ->run();
238 | }
239 |
240 | // Media files
241 | if ($this->hasMedia) {
242 | $f = $this->generateFileList($this->getFiles('media'));
243 |
244 | $this->taskReplaceInFile($xmlFile)
245 | ->from('##MEDIA_FILES##')
246 | ->to($f)
247 | ->run();
248 | }
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Language.php:
--------------------------------------------------------------------------------
1 | adminLangPath = $this->getSourceFolder() . "/administrator/language";
49 | $this->frontLangPath = $this->getSourceFolder() . "/language";
50 |
51 | $this->ext = $extension;
52 |
53 | $this->type = substr($extension, 0, 3);
54 | }
55 |
56 | /**
57 | * Returns true
58 | *
59 | * @return Result
60 | *
61 | * @since 1.0
62 | */
63 | public function run()
64 | {
65 | if (!$this->hasAdminLang && !$this->hasFrontLang) {
66 | // No Language files
67 | return Result::success($this);
68 | }
69 |
70 | $this->printTaskInfo("Building language for " . $this->ext . " | Type " . $this->type);
71 |
72 | // Make sure we have the language folders in our target
73 | $this->prepareDirectories();
74 |
75 | $dest = $this->getBuildFolder();
76 |
77 | if ($this->type == "mod") {
78 | $dest .= "/modules/" . $this->ext;
79 | } elseif ($this->type == "plg") {
80 | $a = explode("_", $this->ext);
81 | $dest .= "/plugins/" . $a[1] . "/" . $a[2];
82 | } elseif ($this->type == "pkg") {
83 | $dest .= "/administrator/manifests/packages/" . $this->ext;
84 | } elseif ($this->type == "lib") {
85 | // Remove lib before - ugly hack
86 | $ex = str_replace("lib_", "", $this->ext);
87 | $dest .= "/libraries/" . $ex;
88 | } elseif ($this->type == "tpl") {
89 | $a = explode("_", $this->ext);
90 | $dest .= "/templates/" . $a[1];
91 | }
92 |
93 | if ($this->hasAdminLang) {
94 | $map = $this->copyLanguage("administrator/language", $dest);
95 | $this->addFiles('backendLanguage', $map);
96 | }
97 |
98 | if ($this->hasFrontLang) {
99 | $map = $this->copyLanguage("language", $dest);
100 | $this->addFiles('frontendLanguage', $map);
101 | }
102 |
103 | $this->printTaskSuccess("Finished building language for " . $this->ext . " | Type " . $this->type);
104 |
105 | return Result::success($this);
106 | }
107 |
108 | /**
109 | * Analyze the extension structure
110 | *
111 | * @return void
112 | *
113 | * @since 1.0
114 | */
115 | private function analyze()
116 | {
117 | // Check for all languages here
118 | if (empty(glob($this->adminLangPath . "/*/*" . $this->ext . "*.ini"))) {
119 | $this->hasAdminLang = false;
120 | }
121 |
122 | if (empty(glob($this->frontLangPath . "/*/*" . $this->ext . "*.ini"))) {
123 | $this->hasFrontLang = false;
124 | }
125 | }
126 |
127 | /**
128 | * Prepare the directory structure
129 | *
130 | * @return boolean
131 | *
132 | * @since 1.0
133 | */
134 | private function prepareDirectories()
135 | {
136 | if ($this->type == "com") {
137 | if ($this->hasAdminLang) {
138 | $this->taskFilesystemStack()
139 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
140 | ->mkdir($this->getBuildFolder() . "/administrator/language")
141 | ->run();
142 | }
143 |
144 | if ($this->hasFrontLang) {
145 | $this->taskFilesystemStack()
146 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
147 | ->mkdir($this->getBuildFolder() . "/language")
148 | ->run();
149 | }
150 | }
151 |
152 | if ($this->type == "mod") {
153 | $this->taskFilesystemStack()
154 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
155 | ->mkdir($this->getBuildFolder() . "/modules/" . $this->ext . "/language")
156 | ->run();
157 | }
158 |
159 | if ($this->type == "plg") {
160 | $a = explode("_", $this->ext);
161 |
162 | $this->taskFilesystemStack()
163 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
164 | ->mkdir($this->getBuildFolder() . "/plugins/" . $a[1] . "/" . $a[2] . "/administrator/language")
165 | ->run();
166 | }
167 |
168 | return true;
169 | }
170 |
171 | /**
172 | * Copy language files
173 | *
174 | * @param string $dir The directory (administrator/language or language or mod_xy/language etc)
175 | * @param String $target The target directory
176 | *
177 | * @return array
178 | *
179 | * @since 1.0
180 | */
181 | public function copyLanguage($dir, $target)
182 | {
183 | // Equals administrator/language or language
184 | $path = $this->getSourceFolder() . "/" . $dir;
185 | $files = [];
186 |
187 | if (!is_dir($path)) {
188 | return $files;
189 | }
190 |
191 | $hdl = opendir($path);
192 |
193 | while ($entry = readdir($hdl)) {
194 | $p = $path . "/" . $entry;
195 |
196 | // Which languages do we have
197 | // Ignore hidden files
198 | if (substr($entry, 0, 1) != '.') {
199 | // Language folders
200 | if (!is_file($p)) {
201 | // Make folder at destination
202 | $this->taskFilesystemStack()
203 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
204 | ->mkdir($target . "/" . $dir . "/" . $entry)
205 | ->run();
206 |
207 | $fileHdl = opendir($p);
208 |
209 | while ($file = readdir($fileHdl)) {
210 | // Only copy language files for this extension (and sys files..)
211 | if (substr($file, 0, 1) !== '.' && strpos($file, $this->ext . ".") !== false) {
212 | $files[] = [$entry => $file];
213 |
214 | // Copy file
215 | $this->taskFilesystemStack()
216 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
217 | ->copy($p . "/" . $file, $target . "/" . $dir . "/" . $entry . "/" . $file)
218 | ->run();
219 | }
220 | }
221 |
222 | closedir($fileHdl);
223 | }
224 | }
225 | }
226 |
227 | closedir($hdl);
228 |
229 | return $files;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Library.php:
--------------------------------------------------------------------------------
1 | new lib
48 | $this->resetFiles();
49 |
50 | $this->libName = $libName;
51 | $this->hasComponent = $hasComponent;
52 |
53 | $this->source = $this->getSourceFolder() . "/libraries/" . $libName;
54 | $this->target = $this->getBuildFolder() . "/libraries/" . $libName;
55 | }
56 |
57 | /**
58 | * Runs the library build tasks, just copying files currently
59 | *
60 | * @return Result
61 | *
62 | * @since 1.0
63 | */
64 | public function run()
65 | {
66 | $this->printTaskInfo("Building library " . $this->libName);
67 |
68 | if (!file_exists($this->source)) {
69 | return Result::error($this, "Folder " . $this->source . " does not exist!");
70 | }
71 |
72 | $this->prepareDirectory();
73 |
74 | // Libaries are problematic.. we have libraries/name/libraries/name in the end for the build script
75 | $tar = $this->target;
76 |
77 | if (!$this->hasComponent) {
78 | $tar = $this->target . "/libraries/" . $this->libName;
79 | }
80 |
81 | $files = $this->copyTarget($this->source, $tar);
82 |
83 | $lib = $this->libName;
84 |
85 | // Workaround for libraries without lib_
86 | if (substr($this->libName, 0, 3) != "lib") {
87 | $lib = 'lib_' . $this->libName;
88 | }
89 |
90 | // Build media (relative path)
91 | $media = $this->buildMedia("media/" . $lib, $lib);
92 | $media->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
93 | ->run();
94 |
95 | $this->addFiles('media', $media->getResultFiles());
96 |
97 | // Build language files for the component
98 | $language = $this->buildLanguage($lib)
99 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
100 | ->run();
101 |
102 | // Copy XML
103 | $this->createInstaller($files);
104 |
105 | $this->printTaskSuccess('Finished building library ' . $this->libName);
106 |
107 | return Result::success($this, "Library build");
108 | }
109 |
110 | /**
111 | * Prepare the directory structure
112 | *
113 | * @return void
114 | *
115 | * @since 1.0
116 | */
117 | private function prepareDirectory()
118 | {
119 | $this->taskFilesystemStack()
120 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
121 | ->mkdir($this->target)
122 | ->run();
123 | }
124 |
125 | /**
126 | * Generate the installer xml file for the library
127 | *
128 | * @param array $files The library files
129 | *
130 | * @return void
131 | *
132 | * @since 1.0
133 | */
134 | private function createInstaller($files)
135 | {
136 | $this->printTaskInfo("Creating library installer");
137 |
138 | $xmlFile = $this->target . "/" . $this->libName . ".xml";
139 |
140 | // Version & Date Replace
141 | $this->replaceInFile($xmlFile);
142 |
143 | // Files and folders
144 | $f = $this->generateFileList($files);
145 |
146 | $this->taskReplaceInFile($xmlFile)
147 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
148 | ->from('##LIBRARYFILES##')
149 | ->to($f)
150 | ->run();
151 |
152 | // Language backend files
153 | $f = $this->generateLanguageFileList($this->getFiles('backendLanguage'));
154 |
155 | $this->taskReplaceInFile($xmlFile)
156 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
157 | ->from('##BACKEND_LANGUAGE_FILES##')
158 | ->to($f)
159 | ->run();
160 |
161 | // Language frontend files
162 | $f = $this->generateLanguageFileList($this->getFiles('frontendLanguage'));
163 |
164 | $this->taskReplaceInFile($xmlFile)
165 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
166 | ->from('##FRONTEND_LANGUAGE_FILES##')
167 | ->to($f)
168 | ->run();
169 |
170 | // Media files
171 | $f = $this->generateFileList($this->getFiles('media'));
172 |
173 | $this->taskReplaceInFile($xmlFile)
174 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
175 | ->from('##MEDIA_FILES##')
176 | ->to($f)
177 | ->run();
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Media.php:
--------------------------------------------------------------------------------
1 | source = $this->getSourceFolder() . "/" . $folder;
45 | $this->extName = $extName;
46 |
47 | $this->type = substr($extName, 0, 3);
48 |
49 | $target = $this->getBuildFolder() . "/" . $folder;
50 |
51 | if ($this->type == 'mod') {
52 | $target = $this->getBuildFolder() . "/modules/" . $extName . "/" . $folder;
53 | } elseif ($this->type == 'plg') {
54 | $a = explode("_", $this->extName);
55 |
56 | $target = $this->getBuildFolder() . "/plugins/" . $a[1] . "/" . $a[2] . "/" . $folder;
57 | } elseif ($this->type == 'lib') {
58 | // Remove lib before - ugly hack
59 | $ex = str_replace("lib_", "", $this->extName);
60 |
61 | $target = $this->getBuildFolder() . "/libraries/" . $ex . "/" . $folder;
62 | }
63 |
64 | $this->target = $target;
65 | }
66 |
67 | /**
68 | * Runs the media build task
69 | *
70 | * @return Result
71 | *
72 | * @since 1.0
73 | */
74 | public function run()
75 | {
76 | $this->printTaskInfo("Building media folder " . $this->source . " for " . $this->extName);
77 |
78 | if (!file_exists($this->source)) {
79 | $this->printTaskInfo("Folder " . $this->source . " does not exist!");
80 |
81 | return Result::success($this);
82 | }
83 |
84 | $this->prepareDirectory();
85 |
86 | $map = $this->copyTarget($this->source, $this->target);
87 |
88 | $this->setResultFiles($map);
89 |
90 | $this->printTaskSuccess("Finished building media folder " . $this->source . " for " . $this->extName);
91 |
92 | return Result::success($this);
93 | }
94 |
95 | /**
96 | * Prepare the directory structure
97 | *
98 | * @return void
99 | *
100 | * @since 1.0
101 | */
102 | private function prepareDirectory()
103 | {
104 | $this->taskFilesystemStack()
105 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
106 | ->mkdir($this->target)
107 | ->run();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Module.php:
--------------------------------------------------------------------------------
1 | new module
45 | $this->resetFiles();
46 |
47 | $this->modName = str_starts_with($modName, 'mod_') ? $modName : 'mod_' . $modName;
48 |
49 | $this->source = $this->getSourceFolder() . "/modules/" . $this->modName;
50 | $this->target = $this->getBuildFolder() . "/modules/" . $this->modName;
51 | }
52 |
53 | /**
54 | * Build the package
55 | *
56 | * @return Result
57 | *
58 | * @since 1.0
59 | */
60 | public function run()
61 | {
62 | $this->printTaskInfo('Building module: ' . $this->modName);
63 |
64 | // Prepare directories
65 | $this->prepareDirectories();
66 |
67 | $files = $this->copyTarget($this->source, $this->target);
68 |
69 | // Build media (relative path)
70 | $media = $this->buildMedia("media/" . $this->modName, $this->modName);
71 | $media->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
72 | ->run();
73 |
74 | $this->addFiles('media', $media->getResultFiles());
75 |
76 | // Build language files for the module
77 | if (is_dir($this->getSourceFolder() . '/language')) {
78 | $language = $this->buildLanguage($this->modName)
79 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
80 | ->run();
81 | }
82 |
83 | // Update XML and script.php
84 | $this->createInstaller($files);
85 |
86 | $this->printTaskSuccess('Finished building module: ' . $this->modName);
87 |
88 | return Result::success($this);
89 | }
90 |
91 | /**
92 | * Prepare the directory structure
93 | *
94 | * @return void
95 | *
96 | * @since 1.0
97 | */
98 | private function prepareDirectories()
99 | {
100 | $this->taskFilesystemStack()
101 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
102 | ->mkdir($this->target)
103 | ->run();
104 | }
105 |
106 | /**
107 | * Generate the installer xml file for the module
108 | *
109 | * @param array $files The module files
110 | *
111 | * @return void
112 | *
113 | * @since 1.0
114 | */
115 | private function createInstaller($files)
116 | {
117 | $this->printTaskInfo("Creating module installer");
118 |
119 | $xmlFile = $this->target . "/" . $this->modName . ".xml";
120 |
121 | // Version & Date Replace
122 | $this->replaceInFile($xmlFile);
123 |
124 | // Files and folders
125 | $f = $this->generateModuleFileList($files, $this->modName);
126 |
127 | $this->taskReplaceInFile($xmlFile)
128 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
129 | ->from('##MODULE_FILES##')
130 | ->to($f)
131 | ->run();
132 |
133 | // Language files
134 | $f = $this->generateLanguageFileList($this->getFiles('frontendLanguage'));
135 |
136 | $this->taskReplaceInFile($xmlFile)
137 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
138 | ->from('##LANGUAGE_FILES##')
139 | ->to($f)
140 | ->run();
141 |
142 | // Media files
143 | $f = $this->generateFileList($this->getFiles('media'));
144 |
145 | $this->taskReplaceInFile($xmlFile)
146 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
147 | ->from('##MEDIA_FILES##')
148 | ->to($f)
149 | ->run();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Package.php:
--------------------------------------------------------------------------------
1 | new package
38 | $this->resetFiles();
39 | }
40 |
41 | /**
42 | * Build the package
43 | *
44 | * @return Result
45 | *
46 | * @since 1.0
47 | */
48 | public function run()
49 | {
50 | $this->printTaskInfo('Building package ' . $this->getExtensionName());
51 |
52 | // Build language files for the package
53 | if (is_dir($this->getSourceFolder() . '/administrator/language')) {
54 | $language = $this->buildLanguage("pkg_" . $this->getExtensionName())
55 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
56 | ->run();
57 | }
58 |
59 | // Update XML and script.php
60 | $this->createInstaller();
61 |
62 | $this->printTaskSuccess('Finished building package ' . $this->getExtensionName());
63 |
64 | return Result::success($this);
65 | }
66 |
67 | /**
68 | * Generate the installer xml file for the package
69 | *
70 | * @return void
71 | *
72 | * @since 1.0
73 | */
74 | private function createInstaller()
75 | {
76 | $this->printTaskInfo("Creating package installer");
77 |
78 | // Copy XML and script.php
79 | $sourceFolder = $this->getSourceFolder() . "/administrator/manifests/packages";
80 | $targetFolder = $this->getBuildFolder() . "/administrator/manifests/packages";
81 | $xmlFile = $targetFolder . "/pkg_" . $this->getExtensionName() . ".xml";
82 | $scriptFile = $targetFolder . "/" . $this->getExtensionName() . "/script.php";
83 |
84 | $this->taskFilesystemStack()
85 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
86 | ->copy($sourceFolder . "/pkg_" . $this->getExtensionName() . ".xml", $xmlFile)
87 | ->run();
88 |
89 | // Version & Date Replace
90 | $this->taskReplaceInFile($xmlFile)
91 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
92 | ->from(['##DATE##', '##YEAR##', '##VERSION##'])
93 | ->to([$this->getDate(), date('Y'), $this->getJConfig()->version])
94 | ->run();
95 |
96 | if (is_file($sourceFolder . "/" . $this->getExtensionName() . "/script.php")) {
97 | $this->taskFilesystemStack()
98 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
99 | ->copy($sourceFolder . "/" . $this->getExtensionName() . "/script.php", $scriptFile)
100 | ->run();
101 |
102 | $this->taskReplaceInFile($scriptFile)
103 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
104 | ->from(['##DATE##', '##YEAR##', '##VERSION##'])
105 | ->to([$this->getDate(), date('Y'), $this->getJConfig()->version])
106 | ->run();
107 | }
108 |
109 | // Language files
110 | $f = $this->generateLanguageFileList($this->getFiles('frontendLanguage'));
111 |
112 | $this->taskReplaceInFile($xmlFile)
113 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
114 | ->from('##LANGUAGE_FILES##')
115 | ->to($f)
116 | ->run();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Plugin.php:
--------------------------------------------------------------------------------
1 | new module
48 | $this->resetFiles();
49 |
50 | $this->plgName = $name;
51 | $this->plgType = $type;
52 |
53 | $this->source = $this->getSourceFolder() . "/plugins/" . $type . "/" . $name;
54 | $this->target = $this->getBuildFolder() . "/plugins/" . $type . "/" . $name;
55 | }
56 |
57 | /**
58 | * Build the package
59 | *
60 | * @return Result
61 | *
62 | * @since 1.0
63 | */
64 | public function run()
65 | {
66 | $this->printTaskInfo('Building plugin: ' . $this->plgName . " (" . $this->plgType . ")");
67 |
68 | // Prepare directories
69 | $this->prepareDirectories();
70 |
71 | $files = $this->copyTarget($this->source, $this->target);
72 |
73 | // Build media (relative path)
74 | $media = $this->buildMedia("media/plg_" . $this->plgType . "_" . $this->plgName, 'plg_' . $this->plgType . "_" . $this->plgName);
75 | $media->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
76 | ->run();
77 |
78 | $this->addFiles('media', $media->getResultFiles());
79 |
80 | // Build language files
81 | if (is_dir($this->getSourceFolder() . '/administrator/language')) {
82 | $this->buildLanguage("plg_" . $this->plgType . "_" . $this->plgName)
83 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
84 | ->run();
85 | }
86 |
87 | // Update XML and script.php
88 | $this->createInstaller($files);
89 |
90 | $this->printTaskSuccess('Finished building plugin: ' . $this->plgName . " (" . $this->plgType . ")");
91 |
92 | return Result::success($this);
93 | }
94 |
95 | /**
96 | * Prepare the directory structure
97 | *
98 | * @return void
99 | *
100 | * @since 1.0
101 | */
102 | private function prepareDirectories()
103 | {
104 | $this->taskFilesystemStack()
105 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
106 | ->mkdir($this->target)
107 | ->run();
108 | }
109 |
110 | /**
111 | * Generate the installer xml file for the plugin
112 | *
113 | * @param array $files The module files
114 | *
115 | * @return void
116 | *
117 | * @since 1.0
118 | */
119 | private function createInstaller($files)
120 | {
121 | $this->printTaskInfo("Creating plugin installer");
122 |
123 | $xmlFile = $this->target . "/" . $this->plgName . ".xml";
124 |
125 | // Version & Date Replace
126 | $this->replaceInFile($xmlFile);
127 |
128 | // Files and folders
129 | $f = $this->generatePluginFileList($files, $this->plgName);
130 |
131 | $this->taskReplaceInFile($xmlFile)
132 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
133 | ->from('##FILES##')
134 | ->to($f)
135 | ->run();
136 |
137 | // Language files
138 | $f = $this->generateLanguageFileList($this->getFiles('backendLanguage'));
139 |
140 | $this->taskReplaceInFile($xmlFile)
141 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
142 | ->from('##LANGUAGE_FILES##')
143 | ->to($f)
144 | ->run();
145 |
146 | // Media files
147 | $f = $this->generateFileList($this->getFiles('media'));
148 |
149 | $this->taskReplaceInFile($xmlFile)
150 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
151 | ->from('##MEDIA_FILES##')
152 | ->to($f)
153 | ->run();
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Tasks.php:
--------------------------------------------------------------------------------
1 | task(Extension::class, $params);
28 | }
29 |
30 | /**
31 | * Build component
32 | *
33 | * @param array $params Opt params
34 | *
35 | * @return CollectionBuilder
36 | *
37 | * @since 1.0
38 | */
39 | protected function buildComponent($name, $params = [])
40 | {
41 | return $this->task(Component::class, $name, $params);
42 | }
43 |
44 | /**
45 | * Build media folder
46 | *
47 | * @param string $source The media folder (an extension could have multiple)
48 | * @param string $extName The extension name (e.g. mod_xy)
49 | *
50 | * @return CollectionBuilder
51 | *
52 | * @since 1.0
53 | */
54 | protected function buildMedia($source, $extName, $params = [])
55 | {
56 | return $this->task(Media::class, $source, $extName, $params);
57 | }
58 |
59 | /**
60 | * Build language folder
61 | *
62 | * @param string $extension The extension (not the whole, but mod_xy or plg_)
63 | *
64 | * @return CollectionBuilder
65 | *
66 | * @since 1.0
67 | */
68 | protected function buildLanguage($extension, $params = [])
69 | {
70 | return $this->task(Language::class, $extension, $params);
71 | }
72 |
73 | /**
74 | * Build a library
75 | *
76 | * @param String $libName Name of the module
77 | * @param array $params Opt params
78 | * @param bool $hasComponent has the extension a component (then we need to build different)
79 | *
80 | * @return CollectionBuilder
81 | *
82 | * @since 1.0
83 | */
84 | protected function buildLibrary($libName, $params, $hasComponent)
85 | {
86 | return $this->task(Library::class, $libName, $params, $hasComponent);
87 | }
88 |
89 | /**
90 | * Build a Module
91 | *
92 | * @param String $modName Name of the module
93 | * @param array $params Opt params
94 | *
95 | * @return CollectionBuilder
96 | *
97 | * @since 1.0
98 | */
99 | protected function buildModule($modName, $params = [])
100 | {
101 | return $this->task(Module::class, $modName, $params);
102 | }
103 |
104 | /**
105 | * Build package
106 | *
107 | * @param array $params Opt params
108 | *
109 | * @return CollectionBuilder
110 | *
111 | * @since 1.0
112 | */
113 | protected function buildPackage($params = [])
114 | {
115 | return $this->task(Package::class, $params);
116 | }
117 |
118 | /**
119 | * Build a Plugin
120 | *
121 | * @param String $type Type of the plugin
122 | * @param String $name Name of the plugin
123 | * @param array $params Opt params
124 | *
125 | * @return CollectionBuilder
126 | *
127 | * @since 1.0
128 | */
129 | protected function buildPlugin($type, $name, $params = [])
130 | {
131 | return $this->task(Plugin::class, $type, $name, $params);
132 | }
133 |
134 | /**
135 | * Build a File extension
136 | *
137 | * @param String $name Name of the plugin
138 | * @param array $params Opt params
139 | *
140 | * @return CollectionBuilder
141 | *
142 | * @since 1.0
143 | */
144 | protected function buildFile($name, $params = [])
145 | {
146 | return $this->task(File::class, $name, $params);
147 | }
148 |
149 | /**
150 | * Build a Template
151 | *
152 | * @param String $templateName Name of the template
153 | * @param array $params Opt params
154 | *
155 | * @return CollectionBuilder
156 | *
157 | * @since 1.0
158 | */
159 | protected function buildTemplate($templateName, $params = [])
160 | {
161 | return $this->task(Template::class, $templateName, $params);
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/Tasks/Build/Template.php:
--------------------------------------------------------------------------------
1 | new template
45 | $this->resetFiles();
46 |
47 | $this->templateName = $templateName;
48 |
49 | $this->source = $this->getSourceFolder() . "/templates/" . $templateName;
50 | $this->target = $this->getBuildFolder() . "/templates/" . $templateName;
51 | }
52 |
53 | /**
54 | * Build the package
55 | *
56 | * @return Result
57 | *
58 | * @since 1.0
59 | */
60 | public function run()
61 | {
62 | $this->printTaskInfo('Building template: ' . $this->templateName);
63 |
64 | // Prepare directories
65 | $this->prepareDirectories();
66 |
67 | $files = $this->copyTarget($this->source, $this->target);
68 |
69 | // Build media (relative path)
70 | $media = $this->buildMedia("media/" . $this->templateName, $this->templateName);
71 | $media->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
72 | ->run();
73 |
74 | $this->addFiles('media', $media->getResultFiles());
75 |
76 | // Build language files for the component
77 | if (is_dir($this->getSourceFolder() . '/language')) {
78 | $language = $this->buildLanguage('tpl_' . $this->templateName)
79 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
80 | ->run();
81 | }
82 |
83 | // Update XML and script.php
84 | $this->createInstaller($files);
85 |
86 | $this->printTaskSuccess('Finished building template: ' . $this->templateName);
87 |
88 | return Result::success($this, "Template build");
89 | }
90 |
91 | /**
92 | * Prepare the directory structure
93 | *
94 | * @return void
95 | *
96 | * @since 1.0
97 | */
98 | private function prepareDirectories()
99 | {
100 | $this->_mkdir($this->target);
101 | }
102 |
103 | /**
104 | * Generate the installer xml file for the template
105 | *
106 | * @param array $files The template files
107 | *
108 | * @return void
109 | *
110 | * @since 1.0
111 | */
112 | private function createInstaller($files)
113 | {
114 | $this->printTaskInfo("Creating template installer");
115 |
116 | $xmlFile = $this->target . "/templateDetails.xml";
117 |
118 | // Version & Date Replace
119 | $this->replaceInFile($xmlFile);
120 |
121 | // Files and folders
122 | $f = $this->generateFileList($files);
123 |
124 | $this->taskReplaceInFile($xmlFile)
125 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
126 | ->from('##TEMPLATE_FILES##')
127 | ->to($f)
128 | ->run();
129 |
130 | // Language files
131 | $f = $this->generateLanguageFileList($this->getFiles('frontendLanguage'));
132 |
133 | $this->taskReplaceInFile($xmlFile)
134 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
135 | ->from('##LANGUAGE_FILES##')
136 | ->to($f)
137 | ->run();
138 |
139 | // Media files
140 | $f = $this->generateFileList($this->getFiles('media'));
141 |
142 | $this->taskReplaceInFile($xmlFile)
143 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE)
144 | ->from('##MEDIA_FILES##')
145 | ->to($f)
146 | ->run();
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Tasks/BumpVersion.php:
--------------------------------------------------------------------------------
1 | printTaskInfo('Updating ' . $this->getJConfig()->extension . " to " . $this->getJConfig()->version);
35 |
36 | // Reusing the header config here
37 | $excludeList = $this->getJConfig()->header->exclude;
38 |
39 | if ($excludeList !== '') {
40 | $exclude = explode(",", trim($excludeList));
41 | }
42 |
43 | $path = realpath($this->getJConfig()->source);
44 | $fileTypes = explode(",", trim($this->getJConfig()->header->files));
45 |
46 | $changedFiles = 0;
47 |
48 | foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)) as $filename) {
49 | if (substr($filename, 0, 1) == '.') {
50 | continue;
51 | }
52 |
53 | $file = new \SplFileInfo($filename);
54 |
55 | if (!in_array($file->getExtension(), $fileTypes)) {
56 | continue;
57 | }
58 |
59 | // Skip directories in exclude list
60 | if (isset($exclude) && count($exclude)) {
61 | $relative = str_replace(realpath($path), "", $file->getPath());
62 |
63 | // It is possible to have multiple exclude directories
64 | foreach ($exclude as $e) {
65 | if (stripos($relative, $e) !== false) {
66 | $this->printTaskInfo("Excluding " . $filename);
67 | continue 2;
68 | }
69 | }
70 | }
71 |
72 | if ($file->getExtension() === 'xml') {
73 | if ($this->updateXML($file)) {
74 | $changedFiles++;
75 | }
76 |
77 | continue;
78 | }
79 |
80 | if ($this->updatePlain($file)) {
81 | $changedFiles++;
82 | }
83 | }
84 |
85 | return Result::success($this, 'Updated ' . $changedFiles . ' files');
86 | }
87 |
88 | protected function updatePlain($file): bool
89 | {
90 | $fileContents = file_get_contents($file->getRealPath());
91 |
92 | if (preg_match('#__DEPLOY_VERSION__#', $fileContents)) {
93 | $fileContents = preg_replace('#__DEPLOY_VERSION__#', $this->getJConfig()->version, $fileContents);
94 |
95 | $this->printTaskInfo('Updating file: ' . $file->getRealPath());
96 |
97 | file_put_contents($file->getRealPath(), $fileContents);
98 |
99 | return true;
100 | }
101 |
102 | return false;
103 | }
104 |
105 | protected function updateXML($file): bool
106 | {
107 | $xml = simplexml_load_file($file->getRealPath());
108 |
109 | if ($xml->getName() !== 'extension') {
110 | return false;
111 | }
112 |
113 | $newVersion = $this->getJConfig()->version;
114 |
115 | if ((string)$xml->version === $newVersion) {
116 | return false;
117 | }
118 |
119 | $xml->version = $newVersion;
120 |
121 | $this->printTaskInfo('Updating file: ' . $file->getRealPath());
122 |
123 | $xml->asXML($file->getRealPath());
124 |
125 | return true;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Tasks/CopyrightHeader.php:
--------------------------------------------------------------------------------
1 | printTaskInfo("Updating / adding copyright headers");
36 | $text = $this->replaceInText(trim($this->getJConfig()->header->text));
37 | $excludeList = $this->getJConfig()->header->exclude;
38 |
39 | if ($excludeList !== '') {
40 | $exclude = explode(",", trim($excludeList));
41 | }
42 |
43 | $path = realpath($this->getJConfig()->source);
44 | $fileTypes = explode(",", trim($this->getJConfig()->header->files));
45 |
46 | foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)) as $filename) {
47 | if (substr($filename, 0, 1) == '.') {
48 | continue;
49 | }
50 |
51 | $file = new \SplFileInfo($filename);
52 |
53 | if (!in_array($file->getExtension(), $fileTypes)) {
54 | continue;
55 | }
56 |
57 | // Skip directories in exclude list
58 | if (isset($exclude) && count($exclude)) {
59 | $relative = str_replace(realpath($path), "", $file->getPath());
60 |
61 | // It is possible to have multiple exclude directories
62 | foreach ($exclude as $e) {
63 | if (stripos($relative, $e) !== false) {
64 | $this->printTaskInfo("Excluding " . $filename);
65 | continue 2;
66 | }
67 | }
68 | }
69 |
70 | // Remove previous / any doctype headers at the beginning of the file
71 | // Todo: needs check for class headers (as long as namespace / use / defined _jexec is there, this is no issue)
72 | $this->removeHeader($file);
73 | $this->addHeader($file, $text);
74 | }
75 |
76 | return Result::success($this, "Finished updating copyright headers");
77 | }
78 |
79 | /**
80 | * Replaces placeholders in the copyright header
81 | * Todo separate and make configurable and extensible
82 | *
83 | * @param string $text The header text with placeholders
84 | *
85 | * @return mixed
86 | *
87 | * @since 1.0
88 | */
89 | protected function replaceInText($text)
90 | {
91 | $text = str_replace("##YEAR##", date('Y'), $text);
92 | $text = str_replace("##DATE##", date('Y-m-d'), $text);
93 |
94 | return $text;
95 | }
96 |
97 | /**
98 | * Remove copyright headers in file (If any)
99 | *
100 | * @param \SplFileInfo $file Target
101 | *
102 | * @return void
103 | *
104 | * @since 1.0
105 | */
106 | protected function removeHeader(\SplFileInfo $file)
107 | {
108 | $content = file_get_contents($file->getRealPath());
109 |
110 | $lines = explode(PHP_EOL, $content);
111 |
112 | foreach ($lines as $i => $l) {
113 | $l = trim($l);
114 |
115 | if (strpos($l, "getRealPath(), implode(PHP_EOL, $lines));
129 | }
130 |
131 | /**
132 | * Adds copyright headers in file
133 | *
134 | * @param \SplFileInfo $file Target
135 | * @param string $text The header text with placeholders
136 | *
137 | * @return void
138 | *
139 | * @since 1.0
140 | */
141 | protected function addHeader(\SplFileInfo $file, $text)
142 | {
143 | $content = file_get_contents($file->getRealPath());
144 |
145 | $lines = explode(PHP_EOL, $content);
146 | $text = explode("\n", $text);
147 |
148 | foreach ($lines as $i => $l) {
149 | $l = trim($l);
150 |
151 | if (strpos($l, "getRealPath(), implode(PHP_EOL, $lines));
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/Tasks/Deploy/Base.php:
--------------------------------------------------------------------------------
1 | printTaskInfo('Uploading ' . $this->getJConfig()->extension . $this->getJConfig()->version . " via FTP");
60 |
61 | // Todo Move filepath and name to config
62 | $this->filename = $this->getExtensionName() . "-" . $this->getJConfig()->version . ".zip";
63 | $this->filepath = $this->params['base'] . "/dist/" . $this->filename;
64 |
65 | // Check if we have a package
66 | if (in_array("package", explode(" ", $this->getJConfig()->target))) {
67 | $this->target = "package";
68 | $this->filename = "pkg-" . $this->getExtensionName() . "-" . $this->getJConfig()->version . ".zip";
69 | $this->filepath = $this->params['base'] . "/dist/" . $this->filename;
70 | }
71 |
72 | try {
73 | if ($this->getJConfig()->ftp->ssl == "true") {
74 | $con = ftp_ssl_connect($this->getJConfig()->ftp->host);
75 | } else {
76 | $con = ftp_connect($this->getJConfig()->ftp->host);
77 | }
78 |
79 | $login_result = ftp_login($con, $this->getJConfig()->ftp->user, $this->getJConfig()->ftp->password);
80 |
81 | // Set passive ftp
82 | ftp_pasv($con, true);
83 |
84 | if (!$login_result) {
85 | return Result::error($this, 'Failed logging in');
86 | }
87 |
88 | ftp_chdir($con, $this->getJConfig()->ftp->target);
89 |
90 | $this->printTaskInfo('Uploading ' . $this->filepath);
91 |
92 | if (!ftp_put($con, $this->filename, $this->filepath, FTP_BINARY)) {
93 | return Result::error($this, 'Failed uploading package');
94 | }
95 |
96 | $this->printTaskInfo("Upload finished");
97 | } catch (\Exception $e) {
98 | return Result::error($this, 'Error: ' . $e->getMessage());
99 | }
100 |
101 | return Result::success($this);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Tasks/Deploy/Package.php:
--------------------------------------------------------------------------------
1 | target = $this->params['base'] . "/dist/pkg-" . $this->getExtensionName() . "-" . $this->getJConfig()->version . ".zip";
53 | $this->current = $this->params['base'] . "/dist/current";
54 | }
55 |
56 | /**
57 | * Build the package
58 | *
59 | * @return Result
60 | *
61 | * @since 1.0
62 | */
63 | public function run()
64 | {
65 | // TODO improve DRY!
66 | $this->printTaskInfo('Creating package ' . $this->getJConfig()->extension . " " . $this->getJConfig()->version);
67 |
68 | // Start getting single archives
69 | if (file_exists($this->params['base'] . '/dist/zips')) {
70 | $this->_deleteDir($this->params['base'] . '/dist/zips');
71 | }
72 |
73 | $this->_mkdir($this->params['base'] . '/dist/zips');
74 | $this->analyze();
75 |
76 | if ($this->hasComponents) {
77 | $this->createComponentZips();
78 | }
79 |
80 | if ($this->hasModules) {
81 | $this->createModuleZips();
82 | }
83 |
84 | if ($this->hasPlugins) {
85 | $this->createPluginZips();
86 | }
87 |
88 | if ($this->hasTemplates) {
89 | $this->createTemplateZips();
90 | }
91 |
92 | if ($this->hasLibraries) {
93 | $this->createLibraryZips();
94 | }
95 |
96 | $this->createPackageZip();
97 |
98 | // Create symlink to current folder
99 | if ($this->isWindows()) {
100 | if (is_file($this->params['base'] . "\dist\pkg-" . $this->getExtensionName() . "-current.zip")) {
101 | unlink($this->params['base'] . "\dist\pkg-" . $this->getExtensionName() . "-current.zip");
102 | }
103 | $this->taskExec('mklink /H "' . $this->params['base'] . "\dist\pkg-" . $this->getExtensionName() . "-current.zip" . '" "' . $this->getWindowsPath($this->target) . '"')
104 | ->run();
105 | } else {
106 | if (is_dir($this->params['base'] . "\dist\pkg-" . $this->getExtensionName() . "-current.zip")) {
107 | unlink($this->params['base'] . "/dist/pkg-" . $this->getExtensionName() . "-current.zip");
108 | }
109 | $this->taskFilesystemStack()
110 | ->symlink($this->target, $this->params['base'] . "/dist/pkg-" . $this->getExtensionName() . "-current.zip")
111 | ->run();
112 | }
113 |
114 | return Result::success($this);
115 | }
116 |
117 | /**
118 | * Check if local OS is Windows
119 | *
120 | * @return boolean
121 | *
122 | * @since 3.7.3
123 | */
124 | private function isWindows()
125 | {
126 | return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
127 | }
128 |
129 | /**
130 | * Return the correct path for Windows (needed by CMD)
131 | *
132 | * @param string $path Linux path
133 | *
134 | * @return string
135 | *
136 | * @since 3.7.3
137 | */
138 | private function getWindowsPath($path)
139 | {
140 | return str_replace('/', DIRECTORY_SEPARATOR, $path);
141 | }
142 |
143 | /**
144 | * Analyze the extension structure
145 | *
146 | * @return void
147 | *
148 | * @since 1.0
149 | */
150 | private function analyze()
151 | {
152 | // Check if we have component, module, plugin etc.
153 | $folders = glob($this->getSourceFolder() . "/administrator/components/com_*", GLOB_ONLYDIR);
154 |
155 | if (count($folders) === 0) {
156 | $this->hasComponents = false;
157 | }
158 |
159 | if (!file_exists($this->current . "/modules")) {
160 | $this->hasModules = false;
161 | }
162 |
163 | if (!file_exists($this->current . "/plugins")) {
164 | $this->hasPlugins = false;
165 | }
166 |
167 | if (!file_exists($this->current . "/templates")) {
168 | $this->hasTemplates = false;
169 | }
170 |
171 | if (!file_exists($this->current . "/libraries")) {
172 | $this->hasLibraries = false;
173 | }
174 | }
175 |
176 | /**
177 | * Add files
178 | *
179 | * @param \ZipArchive $zip The zip object
180 | * @param string $path Optional path
181 | *
182 | * @return void
183 | *
184 | * @since 1.0
185 | */
186 | private function addFiles($zip, $path = null)
187 | {
188 | if (!$path) {
189 | $path = $this->current;
190 | }
191 |
192 | $source = str_replace('\\', '/', realpath($path));
193 |
194 | if (is_dir($source) === true) {
195 | $files = new \RecursiveIteratorIterator(
196 | new \RecursiveDirectoryIterator($source),
197 | \RecursiveIteratorIterator::SELF_FIRST
198 | );
199 |
200 | foreach ($files as $file) {
201 | $file = str_replace('\\', '/', $file);
202 |
203 | if (substr($file, 0, 1) == ".") {
204 | continue;
205 | }
206 |
207 | // Ignore "." and ".." folders
208 | if (in_array(substr($file, strrpos($file, '/') + 1), ['.', '..'])) {
209 | continue;
210 | }
211 |
212 | $file = str_replace('\\', '/', $file);
213 |
214 | if (is_dir($file) === true) {
215 | $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
216 | } elseif (is_file($file) === true) {
217 | $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
218 | }
219 | }
220 | } elseif (is_file($source) === true) {
221 | $zip->addFromString(basename($source), file_get_contents($source));
222 | }
223 | }
224 |
225 | /**
226 | * Create an installable zip file for a component
227 | *
228 | * @return void
229 | *
230 | * @since 1.0
231 | */
232 | public function createComponentZips()
233 | {
234 | $folders = glob($this->getSourceFolder() . "/administrator/components/com_*", GLOB_ONLYDIR);
235 |
236 | foreach ($folders as $folder) {
237 | $cname = basename($folder);
238 | $this->printTaskInfo("Packaging Component " . $cname);
239 | $comZip = new \ZipArchive();
240 | $tmp_path = '/dist/tmp/cbuild';
241 | $componentScriptPath = $this->current . "/administrator/components/" . $cname . "/script.php";
242 |
243 | // Delete old build directory and create new one
244 | if (file_exists($this->params['base'] . $tmp_path)) {
245 | $this->taskFilesystemStack()
246 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
247 | ->remove($this->params['base'] . $tmp_path)
248 | ->run();
249 | }
250 |
251 | $this->taskFilesystemStack()
252 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
253 | ->mkdir($this->params['base'] . $tmp_path)
254 | ->run();
255 |
256 | // Copy code parts of component to build directory
257 | $this->taskFilesystemStack()
258 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
259 | ->mirror($this->current . '/administrator/components/' . $cname, $this->params['base'] . $tmp_path . '/administrator/components/' . $cname)
260 | ->run();
261 |
262 | if (is_dir($this->current . '/components/' . $cname)) {
263 | $this->taskFilesystemStack()
264 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
265 | ->mirror($this->current . '/components/' . $cname, $this->params['base'] . $tmp_path . '/components/' . $cname)
266 | ->run();
267 | }
268 |
269 | if (file_exists($this->current . '/api/components/' . $cname)) {
270 | $this->taskFilesystemStack()
271 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
272 | ->mirror($this->current . '/api/components/' . $cname, $this->params['base'] . $tmp_path . '/api/components/' . $cname)
273 | ->run();
274 | }
275 |
276 | // Copy language files from front- and backend
277 | $backendLanguage = glob($this->getSourceFolder() . '/administrator/language/*/' . $cname . '.ini');
278 |
279 | if (count($backendLanguage) > 0) {
280 | foreach ($backendLanguage as $language) {
281 | $lng = basename(dirname($language));
282 |
283 | if (file_exists($this->getSourceFolder() . '/administrator/language/' . $lng . '/' . $cname . '.ini')) {
284 | $this->taskFilesystemStack()
285 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
286 | ->copy(
287 | $this->getSourceFolder() . '/administrator/language/' . $lng . '/' . $cname . '.ini',
288 | $this->params['base'] . $tmp_path . '/administrator/language/' . $lng . '/' . $cname . '.ini'
289 | )
290 | ->run();
291 | }
292 |
293 | if (file_exists($this->getSourceFolder() . '/administrator/language/' . $lng . '/' . $cname . '.sys.ini')) {
294 | $this->taskFilesystemStack()
295 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
296 | ->copy(
297 | $this->getSourceFolder() . '/administrator/language/' . $lng . '/' . $cname . '.sys.ini',
298 | $this->params['base'] . $tmp_path . '/administrator/language/' . $lng . '/' . $cname . '.sys.ini'
299 | )
300 | ->run();
301 | }
302 | }
303 | }
304 |
305 | $frontendLanguage = glob($this->getSourceFolder() . '/language/*/' . $cname . '.ini');
306 |
307 | if (count($frontendLanguage) > 0) {
308 | foreach ($frontendLanguage as $language) {
309 | $lng = basename(dirname($language));
310 |
311 | if (file_exists($this->getSourceFolder() . '/language/' . $lng . '/' . $cname . '.ini')) {
312 | $this->taskFilesystemStack()
313 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
314 | ->copy(
315 | $this->getSourceFolder() . '/language/' . $lng . '/' . $cname . '.ini',
316 | $this->params['base'] . $tmp_path . '/language/' . $lng . '/' . $cname . '.ini'
317 | )
318 | ->run();
319 | }
320 | }
321 | }
322 |
323 | // Copy media files
324 | if (file_exists($this->current . '/media/' . $cname)) {
325 | $this->taskFilesystemStack()
326 | ->setVerbosityThreshold(self::VERBOSITY_VERY_VERBOSE)
327 | ->mirror($this->current . '/media/' . $cname, $this->params['base'] . $tmp_path . '/media/' . $cname)
328 | ->run();
329 | }
330 |
331 | $comZip->open($this->params['base'] . '/dist/zips/' . $cname . '.zip', \ZipArchive::CREATE);
332 |
333 | // Process the files to zip
334 | $this->addFiles($comZip, $this->params['base'] . $tmp_path);
335 |
336 | $comZip->addFile(
337 | $this->current . "/" . substr($cname, 4) . ".xml",
338 | substr($cname, 4) . ".xml"
339 | );
340 |
341 | if (file_exists($componentScriptPath)) {
342 | $comZip->addFile($componentScriptPath, "script.php");
343 | }
344 |
345 | // Close the zip archive
346 | $comZip->close();
347 | }
348 | }
349 |
350 | /**
351 | * Create zips for libraries
352 | *
353 | * @return void
354 | *
355 | * @since 1.0
356 | */
357 | public function createLibraryZips()
358 | {
359 | $path = $this->current . "/libraries";
360 |
361 | // Get every module
362 | $hdl = opendir($path);
363 |
364 | while ($lib = readdir($hdl)) {
365 | // Only folders
366 | $p = $path . "/" . $lib;
367 |
368 | if (substr($lib, 0, 1) == '.') {
369 | continue;
370 | }
371 |
372 | // Workaround for libraries without lib_
373 | if (substr($lib, 0, 3) != "lib") {
374 | $lib = 'lib_' . $lib;
375 | }
376 |
377 | if (!is_file($p)) {
378 | $this->printTaskInfo("Packaging Library " . $lib);
379 |
380 | // Package file
381 | $zip = new \ZipArchive();
382 |
383 | $zip->open($this->params['base'] . '/dist/zips/' . $lib . '.zip', \ZipArchive::CREATE);
384 |
385 | $this->printTaskInfo("Library " . $p);
386 |
387 | // Process the files to zip
388 | $this->addFiles($zip, $p);
389 |
390 | // Close the zip archive
391 | $zip->close();
392 | }
393 | }
394 |
395 | closedir($hdl);
396 | }
397 |
398 | /**
399 | * Create zips for modules
400 | *
401 | * @return void
402 | *
403 | * @since 1.0
404 | */
405 | public function createModuleZips()
406 | {
407 | $path = $this->current . "/modules";
408 |
409 | // Get every module
410 | $hdl = opendir($path);
411 |
412 | while ($entry = readdir($hdl)) {
413 | // Only folders
414 | $p = $path . "/" . $entry;
415 |
416 | if (substr($entry, 0, 1) == '.') {
417 | continue;
418 | }
419 |
420 | if (!is_file($p)) {
421 | $this->printTaskInfo("Packaging Module " . $entry);
422 |
423 | // Package file
424 | $zip = new \ZipArchive();
425 |
426 | $zip->open($this->params['base'] . '/dist/zips/' . $entry . '.zip', \ZipArchive::CREATE);
427 |
428 | $this->printTaskInfo("Module " . $p);
429 |
430 | // Process the files to zip
431 | $this->addFiles($zip, $p);
432 |
433 | // Close the zip archive
434 | $zip->close();
435 | }
436 | }
437 |
438 | closedir($hdl);
439 | }
440 |
441 | /**
442 | * Create zips for plugins
443 | *
444 | * @return void
445 | *
446 | * @since 1.0
447 | */
448 | public function createPluginZips()
449 | {
450 | $path = $this->current . "/plugins";
451 |
452 | // Get every plugin
453 | $hdl = opendir($path);
454 |
455 | while ($entry = readdir($hdl)) {
456 | // Only folders
457 | $p = $path . "/" . $entry;
458 |
459 | if (substr($entry, 0, 1) == '.') {
460 | continue;
461 | }
462 |
463 | if (!is_file($p)) {
464 | // Plugin type folder
465 | $type = $entry;
466 |
467 | $hdl2 = opendir($p);
468 |
469 | while ($plugin = readdir($hdl2)) {
470 | if (substr($plugin, 0, 1) == '.') {
471 | continue;
472 | }
473 |
474 | // Only folders
475 | $p2 = $path . "/" . $type . "/" . $plugin;
476 |
477 | if (!is_file($p2)) {
478 | $plg = "plg_" . $type . "_" . $plugin;
479 |
480 | $this->printTaskInfo("Packaging Plugin " . $plg);
481 |
482 | // Package file
483 | $zip = new \ZipArchive();
484 |
485 | $zip->open($this->params['base'] . '/dist/zips/' . $plg . '.zip', \ZipArchive::CREATE);
486 |
487 | // Process the files to zip
488 | $this->addFiles($zip, $p2);
489 |
490 | // Close the zip archive
491 | $zip->close();
492 | }
493 | }
494 |
495 | closedir($hdl2);
496 | }
497 | }
498 |
499 | closedir($hdl);
500 | }
501 |
502 | /**
503 | * Create zips for templates
504 | *
505 | * @return void
506 | *
507 | * @since 1.0
508 | */
509 | public function createTemplateZips()
510 | {
511 | $path = $this->current . "/templates";
512 |
513 | // Get every module
514 | $hdl = opendir($path);
515 |
516 | while ($entry = readdir($hdl)) {
517 | // Only folders
518 | $p = $path . "/" . $entry;
519 |
520 | if (substr($entry, 0, 1) == '.') {
521 | continue;
522 | }
523 |
524 | if (!is_file($p)) {
525 | $this->printTaskInfo("Packaging Template " . $entry);
526 |
527 | // Package file
528 | $zip = new \ZipArchive();
529 |
530 | $zip->open($this->params['base'] . '/dist/zips/tpl_' . $entry . '.zip', \ZipArchive::CREATE);
531 |
532 | $this->printTaskInfo("Template " . $p);
533 |
534 | // Process the files to zip
535 | $this->addFiles($zip, $p);
536 |
537 | // Close the zip archive
538 | $zip->close();
539 | }
540 | }
541 |
542 | closedir($hdl);
543 | }
544 |
545 | /**
546 | * Create package zip (called latest)
547 | *
548 | * @return void
549 | *
550 | * @since 1.0
551 | */
552 | public function createPackageZip()
553 | {
554 | $zip = new \ZipArchive();
555 |
556 | // Instantiate the zip archive
557 | $zip->open($this->target, \ZipArchive::CREATE);
558 |
559 | // Process the files to zip
560 | $this->addFiles($zip, $this->params['base'] . '/dist/zips/');
561 |
562 | $pkg_path = $this->current . "/administrator/manifests/packages/pkg_" . $this->getExtensionName();
563 |
564 | $zip->addFile($pkg_path . ".xml", "pkg_" . $this->getExtensionName() . ".xml");
565 |
566 | if (is_file($this->current . "/administrator/manifests/packages/" . $this->getExtensionName() . "/script.php")) {
567 | $zip->addFile(
568 | $this->current . "/administrator/manifests/packages/" . $this->getExtensionName() . "/script.php",
569 | "script.php"
570 | );
571 | }
572 |
573 | // If the package has language files, add those
574 | $pkg_languages_path = $pkg_path . "/language";
575 | $languages = glob($pkg_languages_path . "/*/*pkg_" . $this->getExtensionName() . "*.ini");
576 |
577 | // Add all package language files
578 | foreach ($languages as $lang_path) {
579 | $path_in_zip = substr($lang_path, strlen($pkg_path) + 1);
580 | $zip->addFile($lang_path, $path_in_zip);
581 | }
582 |
583 | // Close the zip archive
584 | $zip->close();
585 | }
586 | }
587 |
--------------------------------------------------------------------------------
/src/Tasks/Deploy/Release.php:
--------------------------------------------------------------------------------
1 | getJConfig()->version;
37 | $remote = $this->getJConfig()->github->remote;
38 | $branch = $this->getJConfig()->github->branch;
39 | $owner = $this->getJConfig()->github->owner;
40 | $repository = $this->getJConfig()->github->repository;
41 |
42 | $this->printTaskInfo('Creating package ' . $this->getJConfig()->extension . " " . $this->getJConfig()->version);
43 |
44 | $latest_release = $this->getLatestReleases();
45 | $pulls = $this->getAllRepoPulls();
46 |
47 | $changes = $this->getChanges($latest_release, $pulls);
48 |
49 | $this->changelogUpdate($changes);
50 |
51 | $this->taskGitStack()
52 | ->add('CHANGELOG.md')
53 | ->commit("Prepare for release version " . $version)
54 | ->push($remote, $branch)
55 | ->run();
56 |
57 | $this->printTaskInfo("Creating github tag: $version");
58 |
59 | $this->taskGitStack()
60 | ->stopOnFail()
61 | ->tag($version)
62 | ->push($remote, $version)
63 | ->run();
64 |
65 | $this->printTaskInfo("Tag created: $version and published at $owner/$repository");
66 |
67 | $this->printTaskInfo("Creating the release at: https://github.com/$owner/$repository/releases/tag/$version");
68 |
69 | $github = $this->getGithub();
70 | $changesInRelease = "# Changelog: \n\n" . implode("\n* ", $changes);
71 |
72 | $response = $github->repositories->releases->create(
73 | $owner,
74 | $repository,
75 | (string) $version,
76 | '',
77 | $this->getJConfig()->extension . " " . $version,
78 | $changesInRelease,
79 | false,
80 | true
81 | );
82 |
83 | $this->printTaskInfo(print_r($repository, true));
84 |
85 | $this->uploadToGithub($version, $this->getJConfig()->github->token, $response->upload_url);
86 |
87 | return Result::success($this);
88 | }
89 |
90 | /**
91 | * Get the Changes
92 | *
93 | * @param object $latest_release Latest release
94 | * @param array $pulls Pulls
95 | *
96 | * @return array
97 | *
98 | * @since 1.0
99 | */
100 | private function getChanges($latest_release, $pulls)
101 | {
102 | $changes = [];
103 |
104 | foreach ($pulls as $pull) {
105 | if (!$latest_release || strtotime($pull->merged_at) > strtotime($latest_release->published_at)) {
106 | if ($this->getJConfig()->github->changelog_source == "pr") {
107 | $changes[] = $pull->title;
108 | }
109 |
110 | $message = explode(PHP_EOL, $pull->commit->message);
111 | $changes[] = $message[0];
112 | }
113 | }
114 |
115 | return $changes;
116 | }
117 |
118 | /**
119 | * Get the latest release
120 | *
121 | * @return false|object
122 | *
123 | * @since 1.0
124 | */
125 | protected function getLatestReleases()
126 | {
127 | $github = $this->getGithub();
128 | $owner = $this->getJConfig()->github->owner;
129 | $repository = $this->getJConfig()->github->repository;
130 |
131 | $this->printTaskInfo('Get latest Release commit ' . $owner . "/" . $repository);
132 |
133 | try {
134 | $latest_release = $github->repositories->releases->get(
135 | $owner,
136 | $repository,
137 | 'latest'
138 | );
139 | } catch (\Exception $e) {
140 | $this->printTaskInfo($owner . "/" . $repository . " has no Release");
141 |
142 | return false;
143 | }
144 |
145 | return $latest_release;
146 | }
147 |
148 | /**
149 | * Get all repository pulls for the changelog
150 | *
151 | * @param string $state The state of the PR (default closed)
152 | * @param string $sha The sha sum (opt)
153 | * @param string $path The path (opt)
154 | * @param string $author The author (opt)
155 | * @param ?\DateTimeInterface $since Changes since (opt)
156 | * @param ?\DateTimeInterface $until Changes until (opt)
157 | *
158 | * @return mixed
159 | *
160 | * @since 1.0
161 | */
162 | protected function getAllRepoPulls($state = 'closed', $sha = '', $path = '', $author = '', \DateTimeInterface $since = null, \DateTimeInterface $until = null)
163 | {
164 | $github = $this->getGithub();
165 |
166 | if (!isset($this->allClosedPulls)) {
167 | if ($this->getJConfig()->github->changelog_source == "pr") {
168 | $this->allClosedPulls = $github->pulls->getList(
169 | $this->getJConfig()->github->owner,
170 | $this->getJConfig()->github->repository,
171 | $state
172 | );
173 | } else {
174 | $this->allClosedPulls = $github->repositories->commits->getList(
175 | $this->getJConfig()->github->owner,
176 | $this->getJConfig()->github->repository,
177 | $sha,
178 | $path,
179 | $author,
180 | $since,
181 | $until
182 | );
183 | }
184 | }
185 |
186 | return $this->allClosedPulls;
187 | }
188 |
189 | /**
190 | * Updates changelog with the changes since the last release
191 | *
192 | * @param string[] $changes The changes
193 | *
194 | * @return void
195 | *
196 | * @since 1.0
197 | */
198 | protected function changelogUpdate($changes)
199 | {
200 | if (!empty($changes)) {
201 | $this->taskChangelog()
202 | ->changes($changes)
203 | ->version($this->getJConfig()->version)
204 | ->run();
205 | }
206 | }
207 |
208 | /**
209 | * Get Github
210 | *
211 | * @return Github
212 | *
213 | * @since 1.0
214 | */
215 | protected function getGithub()
216 | {
217 | $options = new Registry();
218 | $options->set('gh.token', (string) $this->getJConfig()->github->token);
219 |
220 | return new Github($options);
221 | }
222 |
223 | /**
224 | * Upload build Zip- or Packagefile to GitHub
225 | *
226 | * @param string $version The release version
227 | * @param string $githubToken The github access token
228 | * @param string $upload_url The upload URL
229 | *
230 | * @return void
231 | *
232 | * @since 1.0
233 | */
234 | protected function uploadToGithub($version, $githubToken, $upload_url)
235 | {
236 | $deploy = explode(' ', $this->getJConfig()->target);
237 | $zipfile = $this->getExtensionName() . '-' . $this->getJConfig()->version . '.zip';
238 |
239 | if (in_array('package', $deploy)) {
240 | $zipfile = 'pkg-' . $zipfile;
241 | }
242 |
243 | $zipfilepath = $this->params['base'] . '/dist/' . $zipfile;
244 |
245 | $filesize = filesize($zipfilepath);
246 |
247 | $this->printTaskInfo('Uploading the Extension package to the Github release: ' . $version);
248 |
249 | $uploadUrl = str_replace("{?name,label}", "?access_token=" . $githubToken . "&name=" . $zipfile . "&size=" . $filesize, $upload_url);
250 | $request = curl_init($uploadUrl);
251 |
252 | curl_setopt($request, CURLOPT_POST, true);
253 | curl_setopt($request, CURLOPT_VERBOSE, true);
254 |
255 | curl_setopt($request, CURLOPT_HTTPHEADER, ['Authorization: token ' . $githubToken, ]);
256 |
257 | curl_setopt($request, CURLOPT_HTTPHEADER, ['Content-type: application/zip']);
258 | curl_setopt($request, CURLOPT_POSTFIELDS, file_get_contents($zipfilepath));
259 | curl_setopt($request, CURLOPT_SSL_VERIFYHOST, 0);
260 | curl_setopt($request, CURLOPT_SSL_VERIFYPEER, 0);
261 |
262 | $result = curl_exec($request);
263 |
264 | curl_close($request);
265 |
266 | $this->printTaskInfo(print_r($result, true));
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/src/Tasks/Deploy/Tasks.php:
--------------------------------------------------------------------------------
1 | task(Zip::class, $params);
26 | }
27 |
28 | /**
29 | * Build extension
30 | *
31 | * @return CollectionBuilder
32 | *
33 | * @since 1.0
34 | */
35 | protected function deployPackage($params = [])
36 | {
37 | return $this->task(Package::class, $params);
38 | }
39 |
40 | /**
41 | * Build extension
42 | *
43 | * @return CollectionBuilder
44 | *
45 | * @since 1.0
46 | */
47 | protected function deployRelease($params = [])
48 | {
49 | return $this->task(Release::class, $params);
50 | }
51 |
52 | /**
53 | * Deploy to FTP
54 | * (Depends on package or zip deploy task)
55 | *
56 | * @return CollectionBuilder
57 | *
58 | * @since 1.0
59 | */
60 | protected function deployFtp($params = [])
61 | {
62 | return $this->task(FtpUpload::class, $params);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Tasks/Deploy/Zip.php:
--------------------------------------------------------------------------------
1 | target = $this->params['base'] . "/dist/" . $this->getExtensionName() . "-" . $this->getJConfig()->version . ".zip";
37 | $this->zip = new \ZipArchive();
38 | }
39 |
40 | /**
41 | * Build the package
42 | *
43 | * @return Result
44 | *
45 | * @since 1.0
46 | */
47 | public function run()
48 | {
49 | $this->printTaskInfo('Zipping ' . $this->getJConfig()->extension . " " . $this->getJConfig()->version);
50 |
51 | // Instantiate the zip archive
52 | $this->zip->open($this->target, \ZipArchive::CREATE);
53 | $iterator = new \RecursiveIteratorIterator(
54 | new \RecursiveDirectoryIterator($this->getBuildFolder()),
55 | \RecursiveIteratorIterator::SELF_FIRST
56 | );
57 |
58 | // Process the files to zip
59 | foreach ($iterator as $subfolder) {
60 | if ($subfolder->isFile()) {
61 | // Set all separators to forward slashes for comparison
62 | $usefolder = str_replace('\\', '/', $subfolder->getPath());
63 |
64 | // Drop the folder part as we don't want them added to archive
65 | $addpath = str_ireplace($this->getBuildFolder(), '', $usefolder);
66 |
67 | // Remove preceding slash
68 | $findfirst = strpos($addpath, '/');
69 |
70 | if ($findfirst == 0 && $findfirst !== false) {
71 | $addpath = substr($addpath, 1);
72 | }
73 |
74 | if (strlen($addpath) > 0 || empty($addpath)) {
75 | $addpath .= '/';
76 | }
77 |
78 | $options = ['add_path' => $addpath, 'remove_all_path' => true];
79 | $this->zip->addGlob($usefolder . '/*.*', GLOB_BRACE, $options);
80 | }
81 | }
82 |
83 | // Close the zip archive
84 | $this->zip->close();
85 |
86 | return Result::success($this);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Tasks/Generate.php:
--------------------------------------------------------------------------------
1 | prepareSourceDirectory();
43 |
44 | $this->say('Not implemented yet');
45 | }
46 |
47 | /**
48 | * Cleanup the given directory
49 | *
50 | * @param string $dir The dir
51 | *
52 | * @return void
53 | *
54 | * @since 1.0
55 | */
56 | private function cleanup($dir)
57 | {
58 | // Clean building directory
59 | $this->_cleanDir($dir);
60 | }
61 |
62 | /**
63 | * Prepare the directories
64 | *
65 | * @return void
66 | *
67 | * @since 1.0
68 | */
69 | private function prepareSourceDirectory()
70 | {
71 | if (!file_exists($this->sourceFolder)) {
72 | $this->say('Creating source folder');
73 | $this->_mkdir($this->sourceFolder);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Tasks/Generate/Base.php:
--------------------------------------------------------------------------------
1 | adminPath = $this->getSourceFolder() . "/administrator/components/com_" . $this->getExtensionName();
45 | $this->frontPath = $this->getSourceFolder() . "/components/com_" . $this->getExtensionName();
46 | }
47 |
48 | /**
49 | * Build the package
50 | *
51 | * @return Result
52 | *
53 | * @since 1.0
54 | */
55 | public function run()
56 | {
57 | $this->say('Building component');
58 |
59 | // Analyize extension structure
60 | $this->analyze();
61 |
62 | // Prepare directories
63 | $this->prepareDirectories();
64 |
65 | if ($this->hasAdmin) {
66 | $adminFiles = $this->copyTarget($this->adminPath, $this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName());
67 |
68 | $this->addFiles('backend', $adminFiles);
69 | }
70 |
71 | if ($this->hasFront) {
72 | $frontendFiles = $this->copyTarget($this->frontPath, $this->getBuildFolder() . "/components/com_" . $this->getExtensionName());
73 |
74 | $this->addFiles('frontend', $frontendFiles);
75 | }
76 |
77 | // Build media (relative path)
78 | $media = $this->buildMedia("media/com_" . $this->getExtensionName());
79 | $media->run();
80 |
81 | $this->addFiles('media', $media->getResultFiles());
82 |
83 | $language = $this->buildLanguage("com_matukio");
84 | $language->run();
85 |
86 | return Result::success($this, 'Module build');
87 | }
88 |
89 | /**
90 | * Analyze the component structure
91 | *
92 | * @return void
93 | *
94 | * @since 1.0
95 | */
96 | private function analyze()
97 | {
98 | if (!file_exists($this->adminPath)) {
99 | $this->hasAdmin = false;
100 | }
101 |
102 | if (!file_exists($this->frontPath)) {
103 | $this->hasFront = false;
104 | }
105 | }
106 |
107 | /**
108 | * Prepare the directory structure
109 | *
110 | * @return void
111 | *
112 | * @since 1.0
113 | */
114 | private function prepareDirectories()
115 | {
116 | if ($this->hasAdmin) {
117 | $this->_mkdir($this->getBuildFolder() . "/administrator/components/com_" . $this->getExtensionName());
118 | }
119 |
120 | if ($this->hasFront) {
121 | $this->_mkdir($this->getBuildFolder() . "/components/com_" . $this->getExtensionName());
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Tasks/Generate/Package.php:
--------------------------------------------------------------------------------
1 | task(Component::class, $title, $params);
29 | }
30 |
31 | /**
32 | * Generate a module skeleton
33 | *
34 | * @param string $title The module name (e.g. mod_login)
35 | * @param array $params Opt params
36 | *
37 | * @return CollectionBuilder
38 | *
39 | * @since 1.0
40 | */
41 | protected function generateModule($title, $params = [])
42 | {
43 | return $this->task(Module::class, $title, $params);
44 | }
45 |
46 | /**
47 | * Generate a package skeleton
48 | *
49 | * @param string $title The package name (e.g. weblinks)
50 | * @param array $params Opt params
51 | *
52 | * @return CollectionBuilder
53 | *
54 | * @since 1.0
55 | */
56 | protected function generatePackage($title, $params = [])
57 | {
58 | return $this->task(Package::class, $title, $params);
59 | }
60 |
61 | /**
62 | * Generate a plugin skeleton
63 | *
64 | * @param string $title The plugin name (e.g. plg_system_joomla)
65 | * @param array $params Opt params
66 | *
67 | * @return CollectionBuilder
68 | *
69 | * @since 1.0
70 | */
71 | protected function generatePlugin($title, $params = [])
72 | {
73 | return $this->task(Plugin::class, $title, $params);
74 | }
75 |
76 | /**
77 | * Generate a template skeleton
78 | *
79 | * @param string $title The template name (e.g. cassiopeia)
80 | * @param array $params Opt params
81 | *
82 | * @return CollectionBuilder
83 | *
84 | * @since 1.0
85 | */
86 | protected function generateTemplate($title, $params = [])
87 | {
88 | return $this->task(Template::class, $title, $params);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Tasks/Generate/Template.php:
--------------------------------------------------------------------------------
1 | params = (array) $params;
95 | $this->params['base'] = $this->params['base'] ?? \JPATH_BASE;
96 | $this->logger = Robo::logger();
97 |
98 | if (is_a($io, ConsoleIO::class)) {
99 | $this->io = $io;
100 | }
101 |
102 | // Registers the application to run Robo commands
103 | $app = new Application('Joomla\Jorobo\Tasks\JTask', '1.0.0');
104 | Robo::register($app, $this);
105 |
106 | $this->loadConfiguration($params);
107 | $this->determineOperatingSystem();
108 | $this->determineSourceFolder();
109 | }
110 |
111 | /**
112 | * Function to check if folders are existing / writable (Code Base etc.)
113 | *
114 | * @return boolean
115 | *
116 | * @since 1.0
117 | */
118 | public function checkFolders()
119 | {
120 | $dirHandle = opendir($this->getSourceFolder());
121 |
122 | if ($dirHandle === false) {
123 | $this->printTaskError('Can not open ' . $this->getSourceFolder() . ' for parsing');
124 |
125 | return false;
126 | }
127 |
128 | return true;
129 | }
130 |
131 | /**
132 | * Get the operating system
133 | *
134 | * @return string
135 | *
136 | * @since 1.0
137 | */
138 | public function getOs()
139 | {
140 | return $this->os;
141 | }
142 |
143 | /**
144 | * Get the build config
145 | *
146 | * @return \stdClass
147 | *
148 | * @since 1.0
149 | */
150 | public function getJConfig()
151 | {
152 | return self::$jConfig;
153 | }
154 |
155 | /**
156 | * Get the source folder path
157 | *
158 | * @return string absolute path
159 | *
160 | * @since 1.0
161 | */
162 | public function getSourceFolder()
163 | {
164 | return $this->sourceFolder;
165 | }
166 |
167 | /**
168 | * Get the extension name
169 | *
170 | * @return string
171 | *
172 | * @since 1.0
173 | */
174 | public function getExtensionName()
175 | {
176 | if (is_null($this->extension)) {
177 | $this->extension = strtolower($this->getJConfig()->extension);
178 | }
179 |
180 | return $this->extension;
181 | }
182 |
183 | /**
184 | * Set the extension name
185 | *
186 | * @return string
187 | *
188 | * @since 1.0
189 | */
190 | public function setExtensionName($name)
191 | {
192 | $this->extension = strtolower($name);
193 | }
194 |
195 | /**
196 | * Get the destination / build folder
197 | *
198 | * @return string
199 | *
200 | * @since 1.0
201 | */
202 | public function getBuildFolder()
203 | {
204 | return $this->getJConfig()->buildFolder;
205 | }
206 |
207 | /**
208 | * Sets the source folder
209 | *
210 | * @return void
211 | *
212 | * @since 1.0
213 | */
214 | private function determineSourceFolder()
215 | {
216 | $this->sourceFolder = $this->params['base'] . "/" . $this->getJConfig()->source;
217 |
218 | if (!is_dir($this->sourceFolder)) {
219 | $this->printTaskError('Warning - Directory: ' . $this->sourceFolder . ' is not available');
220 | }
221 | }
222 |
223 | /**
224 | * Sets the operating system
225 | *
226 | * @return void
227 | *
228 | * @since 1.0
229 | */
230 | private function determineOperatingSystem()
231 | {
232 | $this->os = strtoupper(substr(PHP_OS, 0, 3));
233 |
234 | if ($this->os === 'WIN') {
235 | $this->fileExtension = '.exe';
236 | }
237 | }
238 |
239 | /**
240 | * Load config
241 | *
242 | * @param array $params Optional Params
243 | *
244 | * @return boolean|void
245 | *
246 | * @since 1.0
247 | * @throws FileNotFoundException
248 | */
249 | private function loadConfiguration($params)
250 | {
251 | if (!is_null(self::$jConfig)) {
252 | return true;
253 | }
254 |
255 | // Load config as object
256 | $jConfig = json_decode(json_encode(parse_ini_file($this->params['base'] . '/jorobo.ini', true)), false);
257 |
258 | if (!$jConfig) {
259 | $this->printTaskError('Error: Config file jorobo.ini not available');
260 |
261 | throw new FileNotFoundException('Config file jorobo.ini not available');
262 | }
263 |
264 | // Are we building a git / dev release?
265 | if ($this->isDevelopmentVersion($params)) {
266 | $res = $this->_exec('git rev-parse --short HEAD');
267 |
268 | $version = "git" . trim($res->getMessage());
269 |
270 | if ($version) {
271 | $this->printTaskInfo("Changing version to development version " . $version);
272 | $jConfig->version = $version;
273 | }
274 | }
275 |
276 | $jConfig->buildFolder = $this->params['base'] . $this->determineTarget($jConfig);
277 | $jConfig->params = $params;
278 |
279 | self::$jConfig = $jConfig;
280 |
281 | // Date set
282 | date_default_timezone_set('UTC');
283 | }
284 |
285 | /**
286 | * Check if we are building a dev release
287 | *
288 | * @param array $params Robo.li Params
289 | *
290 | * @return boolean
291 | *
292 | * @since 1.0
293 | */
294 | private function isDevelopmentVersion($params)
295 | {
296 | return isset($params['dev']) ? $params['dev'] : false;
297 | }
298 |
299 | /**
300 | * Get target
301 | *
302 | * @param object $jConfig The JoRobo config
303 | *
304 | * @return string
305 | *
306 | * @since 1.0
307 | */
308 | private function determineTarget($jConfig)
309 | {
310 | if (!isset($jConfig->extension)) {
311 | return 'unnamed';
312 | }
313 |
314 | $target = "/dist/" . $jConfig->extension;
315 |
316 | if (!empty($jConfig->version)) {
317 | $target = "/dist/" . $jConfig->extension . "-" . $jConfig->version;
318 |
319 | return $target;
320 | }
321 |
322 | return $target;
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/src/Tasks/Map.php:
--------------------------------------------------------------------------------
1 | target = $target;
56 | }
57 |
58 | /**
59 | * Maps all parts of an extension into a Joomla! installation
60 | *
61 | * @return Result
62 | *
63 | * @since 1.0
64 | */
65 | public function run()
66 | {
67 | $this->printTaskInfo('Mapping ' . $this->getJConfig()->extension . " to " . $this->target);
68 | $this->printTaskInfo('OS: ' . $this->getOs() . " | Basedir: " . $this->getSourceFolder());
69 |
70 | if (!$this->checkFolders()) {
71 | return Result::error($this, 'checkFolders failed');
72 | }
73 |
74 | $dirHandle = opendir($this->getSourceFolder());
75 |
76 | // Get all main dirs
77 | while (false !== ($element = readdir($dirHandle))) {
78 | if (substr($element, 0, 1) == '.') {
79 | continue;
80 | }
81 |
82 | $method = 'process' . ucfirst($element);
83 |
84 | if (method_exists($this, $method)) {
85 | $this->$method($this->getSourceFolder() . "/" . $element, $this->target);
86 | } else {
87 | $this->printTaskInfo('Missing method: ' . $method);
88 | }
89 | }
90 |
91 | closedir($dirHandle);
92 |
93 | return Result::success($this, "Finished symlinking into Joomla!");
94 | }
95 |
96 | /**
97 | * Process Administrator files
98 | *
99 | * @return void
100 | *
101 | * @since 1.0
102 | */
103 | private function processAdministrator()
104 | {
105 | $sourceFolder = $this->getSourceFolder();
106 | $this->processComponents($sourceFolder . '/administrator/components', $this->target . '/administrator');
107 | $this->processLanguage($sourceFolder . '/administrator/language', $this->target . '/administrator');
108 | $this->processModules($sourceFolder . '/administrator/modules', $this->target . '/administrator/modules');
109 | }
110 |
111 | /**
112 | * Process components
113 | *
114 | * @param String $src The source
115 | * @param String $to The target
116 | *
117 | * @return void
118 | *
119 | * @since 1.0
120 | */
121 | private function processComponents($src, $to)
122 | {
123 | // Component directory
124 | if (is_dir($src)) {
125 | $dirHandle = opendir($src);
126 |
127 | while (false !== ($element = readdir($dirHandle))) {
128 | if (false !== strpos($element, 'com_')) {
129 | $this->symlink($src . '/' . $element, $to . '/components/' . $element);
130 | }
131 | }
132 | }
133 | }
134 |
135 | /**
136 | * Process components
137 | *
138 | * @param String $src The source
139 | * @param String $toDir The target
140 | *
141 | * @return void
142 | *
143 | * @since 1.0
144 | */
145 | private function processLanguage($src, $toDir)
146 | {
147 | if (is_dir($src)) {
148 | $dirHandle = opendir($src);
149 |
150 | while (false !== ($element = readdir($dirHandle))) {
151 | if (substr($element, 0, 1) != '.') {
152 | if (is_dir($src . "/" . $element)) {
153 | $langDirHandle = opendir($src . '/' . $element);
154 |
155 | while (false !== ($file = readdir($langDirHandle))) {
156 | if (is_file($src . '/' . $element . '/' . $file)) {
157 | $this->printTaskInfo($file);
158 | $this->symlink($src . '/' . $element . '/' . $file, $toDir . '/language/' . $element . '/' . $file);
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
165 | }
166 |
167 | /**
168 | * Process Libraries
169 | *
170 | * @param String $src The source
171 | * @param String $toDir The target
172 | *
173 | * @return void
174 | *
175 | * @since 1.0
176 | */
177 | private function processLibraries($src, $toDir)
178 | {
179 | $this->linkSubdirectories($src, $toDir . "/libraries");
180 | }
181 |
182 | /**
183 | * Process media
184 | *
185 | * @param String $src The source
186 | * @param String $toDir The target
187 | *
188 | * @return void
189 | *
190 | * @since 1.0
191 | */
192 | private function processMedia($src, $toDir)
193 | {
194 | $this->linkSubdirectories($src, $toDir . "/media");
195 | }
196 |
197 | /**
198 | * Link subdirectories into folder
199 | *
200 | * @param string $src The source
201 | * @param string $to The target
202 | *
203 | * @return void
204 | *
205 | * @since 1.0
206 | */
207 | private function linkSubdirectories($src, $to)
208 | {
209 | if (is_dir($src)) {
210 | $dirHandle = opendir($src);
211 |
212 | while (false !== ($element = readdir($dirHandle))) {
213 | if (substr($element, 0, 1) != '.') {
214 | if (is_dir($src . "/" . $element)) {
215 | $this->symlink($src . "/" . $element, $to . '/' . $element);
216 | }
217 | }
218 | }
219 | }
220 | }
221 |
222 | /**
223 | * Process Module
224 | *
225 | * @param String $src The source
226 | * @param String $toDir The target
227 | *
228 | * @return void
229 | *
230 | * @since 1.0
231 | */
232 | private function processModules($src, $toDir)
233 | {
234 | $this->linkSubdirectories($src, $toDir . "/modules");
235 | }
236 |
237 | /**
238 | * Process Plugins
239 | *
240 | * @param String $src The source
241 | * @param String $toDir The target
242 | *
243 | * @return void
244 | *
245 | * @since 1.0
246 | */
247 | private function processPlugins($src, $toDir)
248 | {
249 | // Plugin folder /plugins
250 | if (is_dir($src)) {
251 | $dirHandle = opendir($src);
252 |
253 | while (false !== ($type = readdir($dirHandle))) {
254 | if (substr($type, 0, 1) != '.') {
255 | if (is_dir($src . "/" . $type)) {
256 | $this->linkSubdirectories($src . "/" . $type, $toDir . '/plugins/' . $type);
257 | }
258 | }
259 | }
260 | }
261 | }
262 |
263 | /**
264 | * Process components
265 | *
266 | * @param String $type The type
267 | * @param String $toDir The target
268 | *
269 | * @return void
270 | *
271 | * @since 1.0
272 | */
273 | private function mapDir($type, $toDir)
274 | {
275 | // Check if dir exists
276 | if (is_dir($this->getSourceFolder())) {
277 | $dirHandle = opendir($this->getSourceFolder());
278 |
279 | while (false !== ($element = readdir($dirHandle))) {
280 | if (substr($element, 0, 1) != '.') {
281 | $this->symlink($this->getSourceFolder() . '/' . $element, $toDir . '/' . $type . '/' . $element);
282 | }
283 | }
284 | }
285 | }
286 |
287 | /**
288 | * Symlinks files / folders
289 | *
290 | * @param String $source The source
291 | * @param String $target The target
292 | *
293 | * @return void
294 | *
295 | * @since 1.0
296 | */
297 | private function symlink($source, $target)
298 | {
299 | if (file_exists($target)) {
300 | if (is_dir($target)) {
301 | $this->_deleteDir($target);
302 | } else {
303 | unlink($target);
304 | }
305 | }
306 |
307 | try {
308 | $this->taskFileSystemStack()
309 | ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERBOSE)
310 | ->symlink($source, $target)
311 | ->run();
312 | } catch (\Exception $e) {
313 | $this->printTaskError('Error symlinking: ' . $e->getMessage());
314 | }
315 | }
316 | }
317 |
--------------------------------------------------------------------------------
/src/Tasks/Tasks.php:
--------------------------------------------------------------------------------
1 | task(Map::class, $target, $params);
28 | }
29 |
30 | /**
31 | * The build task
32 | *
33 | * @param array $params Opt params
34 | *
35 | * @return CollectionBuilder
36 | *
37 | * @since 1.0
38 | */
39 | protected function taskBuild($params = [])
40 | {
41 | return $this->task(Build::class, $params);
42 | }
43 |
44 | /**
45 | * The generate task
46 | *
47 | * @param array $params Opt params
48 | *
49 | * @return CollectionBuilder
50 | *
51 | * @since 1.0
52 | */
53 | protected function taskGenerate($params = [])
54 | {
55 | return $this->task(Generate::class, $params);
56 | }
57 |
58 | /**
59 | * The CopyrightHeader task
60 | *
61 | * @param array $params Opt params
62 | *
63 | * @return CollectionBuilder
64 | *
65 | * @since 1.0
66 | */
67 | protected function taskCopyrightHeaders($params = [])
68 | {
69 | return $this->task(CopyrightHeader::class, $params);
70 | }
71 |
72 | /**
73 | * Bump the __DEPLOY_VERSION__ task
74 | *
75 | * @param array $params Opt params
76 | * *
77 | * @return CollectionBuilder
78 | *
79 | * @since 1.0
80 | */
81 | protected function taskBumpVersion($params = [])
82 | {
83 | return $this->task(BumpVersion::class, $params);
84 | }
85 |
86 | /**
87 | * Generate joomla.asset.json files
88 | *
89 | * @param $params
90 | *
91 | * @return CollectionBuilder
92 | */
93 | protected function taskGenerateAssetJSON($params = [])
94 | {
95 | return $this->task(AssetJSON::class, $params);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------