├── .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 | [![Latest Stable Version](https://poser.pugx.org/joomla-projects/jorobo/v/stable)](https://packagist.org/packages/joomla-projects/jorobo) [![Total Downloads](https://poser.pugx.org/joomla-projects/jorobo/downloads)](https://packagist.org/packages/joomla-projects/jorobo) [![License](https://poser.pugx.org/joomla-projects/jorobo/license)](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 . ""; 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 . ""; 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 . ""; 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 | --------------------------------------------------------------------------------