├── .gitattributes
├── .github
├── issue_template.md
└── workflows
│ ├── index-wiki.yml
│ └── pull-request.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bin
├── .files
│ ├── joomla2.users.sql
│ ├── joomla3.users.sql
│ └── vhosts
│ │ └── apache.conf
└── joomla
├── composer.json
├── composer.lock
├── screenshot.png
└── src
└── Joomlatools
└── Console
├── Application.php
├── Command
├── Configurable.php
├── Database
│ ├── AbstractDatabase.php
│ ├── Drop.php
│ ├── Export.php
│ └── Install.php
├── Extension
│ ├── AbstractExtension.php
│ ├── Install.php
│ ├── Iterator
│ │ └── Iterator.php
│ ├── Register.php
│ └── Symlink.php
├── Plugin
│ ├── Install.php
│ ├── ListAll.php
│ └── Uninstall.php
├── Site
│ ├── AbstractSite.php
│ ├── Configure.php
│ ├── Create.php
│ ├── Delete.php
│ ├── Download.php
│ ├── Export.php
│ ├── Install.php
│ └── Listing.php
├── Versions.php
└── Vhost
│ ├── Create.php
│ └── Remove.php
├── Joomla
├── Application.php
├── Bootstrapper.php
├── Cache.php
└── Util.php
└── Symlinkers
├── joomlatools-components.php
└── joomlatools-framework.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | bin/joomla text eol=lf
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
14 |
15 | ### Describe your environment
16 |
17 | * OS: _____
18 | * Joomla version: _____
19 | * Joomlatools Console version: _____
20 | * Are you working on the Joomlatools Vagrant: yes/no
21 | * Are you working on the Joomlatools Server: yes/no
22 |
23 | ### Describe the problem:
24 |
25 | #### Steps to reproduce:
26 |
27 | 1. _____
28 | 2. _____
29 |
30 | #### Observed Results:
31 |
32 | * What happened? This could be a description, log output, etc.
33 |
34 | #### Expected Results:
35 |
36 | * What did you expect to happen?
37 |
38 | #### Relevant Code:
39 |
40 | ```
41 | // TODO(you): code here to reproduce the problem
42 | ```
43 |
--------------------------------------------------------------------------------
/.github/workflows/index-wiki.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Index Wiki
3 |
4 | on:
5 | gollum:
6 | workflow_dispatch:
7 |
8 | jobs:
9 | index:
10 |
11 | name: Create Wiki Filesystem Index
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Checkout wiki code from Github
16 | uses: actions/checkout@v3
17 | with:
18 | repository: ${{github.repository}}.wiki
19 | fetch-depth: 0
20 |
21 | - name: Create index
22 | run: |
23 | git ls-files -z | xargs -0 -n1 -I{} -- git log -1 --format="{},%h,%aI,%aN,%aE" {} | \
24 | jq -Rs 'split("\n")[:-1] | map({
25 | file: (. | split(",")[0]),
26 | hash: (. | split(",")[1]),
27 | author_date: (. | split(",")[2]),
28 | author_name: (. | split(",")[3]),
29 | author_email: (. | split(",")[4]),
30 | })' > _Index.json
31 |
32 | - name: Commit index
33 | uses: stefanzweifel/git-auto-commit-action@v4
34 | with:
35 | commit_message: Update Wiki Index
36 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Create a pull request
3 |
4 | on:
5 | create:
6 |
7 | jobs:
8 | pull-request:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 |
13 |
14 | - name: Only run on branches whose names start with feature/ or hotfix/
15 | if: startsWith(github.ref, 'refs/heads/feature') || startsWith(github.ref, 'refs/heads/hotfix')
16 | shell: bash
17 | run: echo "ISSUE_ID=$(echo $GITHUB_REF | grep -o -E '[0-9]+' | head -1 | sed -e 's/^0\+//')" >> $GITHUB_ENV
18 |
19 |
20 | - name: Get issue data
21 | uses: octokit/request-action@v2.x
22 | if: ${{ env.ISSUE_ID }}
23 | id: issue
24 | with:
25 | route: GET /repos/${{ github.repository }}/issues/${{ env.ISSUE_ID }}
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 |
29 |
30 | - name: Gather PR data
31 | if: ${{ steps.issue.outputs.data }}
32 | id: pr_data
33 | shell: bash
34 | run: |
35 | echo "::set-output name=title::$(echo $json_var | jq '.title' --raw-output )"
36 | echo "::set-output name=body::Closes #$(echo $json_var | jq '.number' --raw-output)"
37 | echo "::set-output name=assignee::$(echo $json_var | jq '[.assignees[].login] | join(",")' --raw-output --compact-output)"
38 | echo "::set-output name=label::$(echo $json_var | jq '[.labels[].name] | join(",")' --raw-output --compact-output)"
39 | echo "::set-output name=milestone::$(echo $json_var | jq '[.milestone.title] | join(",")' --raw-output --compact-output)"
40 | env:
41 | json_var: ${{ steps.issue.outputs.data }}
42 |
43 |
44 | - name: Create pull request
45 | if: ${{ steps.issue.outputs.data }}
46 | uses: repo-sync/pull-request@v2
47 | with:
48 | pr_title: ${{ steps.pr_data.outputs.title }}
49 | pr_body: ${{ steps.pr_data.outputs.body }}
50 | pr_assignee: ${{ steps.pr_data.outputs.assignee }}
51 | pr_label: ${{ steps.pr_data.outputs.label }}
52 | pr_milestone: ${{ steps.pr_data.outputs.milestone }}
53 | github_token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /bin/.files/cache
3 | /plugins/*
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Please read [Contributing to Joomlatools Projects](http://developer.joomlatools.com/contribute.html)
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License, version 2.0
2 |
3 | 1. Definitions
4 |
5 | 1.1. “Contributor”
6 |
7 | means each individual or legal entity that creates, contributes to the
8 | creation of, or owns Covered Software.
9 |
10 | 1.2. “Contributor Version”
11 |
12 | means the combination of the Contributions of others (if any) used by a
13 | Contributor and that particular Contributor’s Contribution.
14 |
15 | 1.3. “Contribution”
16 |
17 | means Covered Software of a particular Contributor.
18 |
19 | 1.4. “Covered Software”
20 |
21 | means Source Code Form to which the initial Contributor has attached the
22 | notice in Exhibit A, the Executable Form of such Source Code Form, and
23 | Modifications of such Source Code Form, in each case including portions
24 | thereof.
25 |
26 | 1.5. “Incompatible With Secondary Licenses”
27 | means
28 |
29 | a. that the initial Contributor has attached the notice described in
30 | Exhibit B to the Covered Software; or
31 |
32 | b. that the Covered Software was made available under the terms of version
33 | 1.1 or earlier of the License, but not also under the terms of a
34 | Secondary License.
35 |
36 | 1.6. “Executable Form”
37 |
38 | means any form of the work other than Source Code Form.
39 |
40 | 1.7. “Larger Work”
41 |
42 | means a work that combines Covered Software with other material, in a separate
43 | file or files, that is not Covered Software.
44 |
45 | 1.8. “License”
46 |
47 | means this document.
48 |
49 | 1.9. “Licensable”
50 |
51 | means having the right to grant, to the maximum extent possible, whether at the
52 | time of the initial grant or subsequently, any and all of the rights conveyed by
53 | this License.
54 |
55 | 1.10. “Modifications”
56 |
57 | means any of the following:
58 |
59 | a. any file in Source Code Form that results from an addition to, deletion
60 | from, or modification of the contents of Covered Software; or
61 |
62 | b. any new file in Source Code Form that contains any Covered Software.
63 |
64 | 1.11. “Patent Claims” of a Contributor
65 |
66 | means any patent claim(s), including without limitation, method, process,
67 | and apparatus claims, in any patent Licensable by such Contributor that
68 | would be infringed, but for the grant of the License, by the making,
69 | using, selling, offering for sale, having made, import, or transfer of
70 | either its Contributions or its Contributor Version.
71 |
72 | 1.12. “Secondary License”
73 |
74 | means either the GNU General Public License, Version 2.0, the GNU Lesser
75 | General Public License, Version 2.1, the GNU Affero General Public
76 | License, Version 3.0, or any later versions of those licenses.
77 |
78 | 1.13. “Source Code Form”
79 |
80 | means the form of the work preferred for making modifications.
81 |
82 | 1.14. “You” (or “Your”)
83 |
84 | means an individual or a legal entity exercising rights under this
85 | License. For legal entities, “You” includes any entity that controls, is
86 | controlled by, or is under common control with You. For purposes of this
87 | definition, “control” means (a) the power, direct or indirect, to cause
88 | the direction or management of such entity, whether by contract or
89 | otherwise, or (b) ownership of more than fifty percent (50%) of the
90 | outstanding shares or beneficial ownership of such entity.
91 |
92 |
93 | 2. License Grants and Conditions
94 |
95 | 2.1. Grants
96 |
97 | Each Contributor hereby grants You a world-wide, royalty-free,
98 | non-exclusive license:
99 |
100 | a. under intellectual property rights (other than patent or trademark)
101 | Licensable by such Contributor to use, reproduce, make available,
102 | modify, display, perform, distribute, and otherwise exploit its
103 | Contributions, either on an unmodified basis, with Modifications, or as
104 | part of a Larger Work; and
105 |
106 | b. under Patent Claims of such Contributor to make, use, sell, offer for
107 | sale, have made, import, and otherwise transfer either its Contributions
108 | or its Contributor Version.
109 |
110 | 2.2. Effective Date
111 |
112 | The licenses granted in Section 2.1 with respect to any Contribution become
113 | effective for each Contribution on the date the Contributor first distributes
114 | such Contribution.
115 |
116 | 2.3. Limitations on Grant Scope
117 |
118 | The licenses granted in this Section 2 are the only rights granted under this
119 | License. No additional rights or licenses will be implied from the distribution
120 | or licensing of Covered Software under this License. Notwithstanding Section
121 | 2.1(b) above, no patent license is granted by a Contributor:
122 |
123 | a. for any code that a Contributor has removed from Covered Software; or
124 |
125 | b. for infringements caused by: (i) Your and any other third party’s
126 | modifications of Covered Software, or (ii) the combination of its
127 | Contributions with other software (except as part of its Contributor
128 | Version); or
129 |
130 | c. under Patent Claims infringed by Covered Software in the absence of its
131 | Contributions.
132 |
133 | This License does not grant any rights in the trademarks, service marks, or
134 | logos of any Contributor (except as may be necessary to comply with the
135 | notice requirements in Section 3.4).
136 |
137 | 2.4. Subsequent Licenses
138 |
139 | No Contributor makes additional grants as a result of Your choice to
140 | distribute the Covered Software under a subsequent version of this License
141 | (see Section 10.2) or under the terms of a Secondary License (if permitted
142 | under the terms of Section 3.3).
143 |
144 | 2.5. Representation
145 |
146 | Each Contributor represents that the Contributor believes its Contributions
147 | are its original creation(s) or it has sufficient rights to grant the
148 | rights to its Contributions conveyed by this License.
149 |
150 | 2.6. Fair Use
151 |
152 | This License is not intended to limit any rights You have under applicable
153 | copyright doctrines of fair use, fair dealing, or other equivalents.
154 |
155 | 2.7. Conditions
156 |
157 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
158 | Section 2.1.
159 |
160 |
161 | 3. Responsibilities
162 |
163 | 3.1. Distribution of Source Form
164 |
165 | All distribution of Covered Software in Source Code Form, including any
166 | Modifications that You create or to which You contribute, must be under the
167 | terms of this License. You must inform recipients that the Source Code Form
168 | of the Covered Software is governed by the terms of this License, and how
169 | they can obtain a copy of this License. You may not attempt to alter or
170 | restrict the recipients’ rights in the Source Code Form.
171 |
172 | 3.2. Distribution of Executable Form
173 |
174 | If You distribute Covered Software in Executable Form then:
175 |
176 | a. such Covered Software must also be made available in Source Code Form,
177 | as described in Section 3.1, and You must inform recipients of the
178 | Executable Form how they can obtain a copy of such Source Code Form by
179 | reasonable means in a timely manner, at a charge no more than the cost
180 | of distribution to the recipient; and
181 |
182 | b. You may distribute such Executable Form under the terms of this License,
183 | or sublicense it under different terms, provided that the license for
184 | the Executable Form does not attempt to limit or alter the recipients’
185 | rights in the Source Code Form under this License.
186 |
187 | 3.3. Distribution of a Larger Work
188 |
189 | You may create and distribute a Larger Work under terms of Your choice,
190 | provided that You also comply with the requirements of this License for the
191 | Covered Software. If the Larger Work is a combination of Covered Software
192 | with a work governed by one or more Secondary Licenses, and the Covered
193 | Software is not Incompatible With Secondary Licenses, this License permits
194 | You to additionally distribute such Covered Software under the terms of
195 | such Secondary License(s), so that the recipient of the Larger Work may, at
196 | their option, further distribute the Covered Software under the terms of
197 | either this License or such Secondary License(s).
198 |
199 | 3.4. Notices
200 |
201 | You may not remove or alter the substance of any license notices (including
202 | copyright notices, patent notices, disclaimers of warranty, or limitations
203 | of liability) contained within the Source Code Form of the Covered
204 | Software, except that You may alter any license notices to the extent
205 | required to remedy known factual inaccuracies.
206 |
207 | 3.5. Application of Additional Terms
208 |
209 | You may choose to offer, and to charge a fee for, warranty, support,
210 | indemnity or liability obligations to one or more recipients of Covered
211 | Software. However, You may do so only on Your own behalf, and not on behalf
212 | of any Contributor. You must make it absolutely clear that any such
213 | warranty, support, indemnity, or liability obligation is offered by You
214 | alone, and You hereby agree to indemnify every Contributor for any
215 | liability incurred by such Contributor as a result of warranty, support,
216 | indemnity or liability terms You offer. You may include additional
217 | disclaimers of warranty and limitations of liability specific to any
218 | jurisdiction.
219 |
220 | 4. Inability to Comply Due to Statute or Regulation
221 |
222 | If it is impossible for You to comply with any of the terms of this License
223 | with respect to some or all of the Covered Software due to statute, judicial
224 | order, or regulation then You must: (a) comply with the terms of this License
225 | to the maximum extent possible; and (b) describe the limitations and the code
226 | they affect. Such description must be placed in a text file included with all
227 | distributions of the Covered Software under this License. Except to the
228 | extent prohibited by statute or regulation, such description must be
229 | sufficiently detailed for a recipient of ordinary skill to be able to
230 | understand it.
231 |
232 | 5. Termination
233 |
234 | 5.1. The rights granted under this License will terminate automatically if You
235 | fail to comply with any of its terms. However, if You become compliant,
236 | then the rights granted under this License from a particular Contributor
237 | are reinstated (a) provisionally, unless and until such Contributor
238 | explicitly and finally terminates Your grants, and (b) on an ongoing basis,
239 | if such Contributor fails to notify You of the non-compliance by some
240 | reasonable means prior to 60 days after You have come back into compliance.
241 | Moreover, Your grants from a particular Contributor are reinstated on an
242 | ongoing basis if such Contributor notifies You of the non-compliance by
243 | some reasonable means, this is the first time You have received notice of
244 | non-compliance with this License from such Contributor, and You become
245 | compliant prior to 30 days after Your receipt of the notice.
246 |
247 | 5.2. If You initiate litigation against any entity by asserting a patent
248 | infringement claim (excluding declaratory judgment actions, counter-claims,
249 | and cross-claims) alleging that a Contributor Version directly or
250 | indirectly infringes any patent, then the rights granted to You by any and
251 | all Contributors for the Covered Software under Section 2.1 of this License
252 | shall terminate.
253 |
254 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
255 | license agreements (excluding distributors and resellers) which have been
256 | validly granted by You or Your distributors under this License prior to
257 | termination shall survive termination.
258 |
259 | 6. Disclaimer of Warranty
260 |
261 | Covered Software is provided under this License on an “as is” basis, without
262 | warranty of any kind, either expressed, implied, or statutory, including,
263 | without limitation, warranties that the Covered Software is free of defects,
264 | merchantable, fit for a particular purpose or non-infringing. The entire
265 | risk as to the quality and performance of the Covered Software is with You.
266 | Should any Covered Software prove defective in any respect, You (not any
267 | Contributor) assume the cost of any necessary servicing, repair, or
268 | correction. This disclaimer of warranty constitutes an essential part of this
269 | License. No use of any Covered Software is authorized under this License
270 | except under this disclaimer.
271 |
272 | 7. Limitation of Liability
273 |
274 | Under no circumstances and under no legal theory, whether tort (including
275 | negligence), contract, or otherwise, shall any Contributor, or anyone who
276 | distributes Covered Software as permitted above, be liable to You for any
277 | direct, indirect, special, incidental, or consequential damages of any
278 | character including, without limitation, damages for lost profits, loss of
279 | goodwill, work stoppage, computer failure or malfunction, or any and all
280 | other commercial damages or losses, even if such party shall have been
281 | informed of the possibility of such damages. This limitation of liability
282 | shall not apply to liability for death or personal injury resulting from such
283 | party’s negligence to the extent applicable law prohibits such limitation.
284 | Some jurisdictions do not allow the exclusion or limitation of incidental or
285 | consequential damages, so this exclusion and limitation may not apply to You.
286 |
287 | 8. Litigation
288 |
289 | Any litigation relating to this License may be brought only in the courts of
290 | a jurisdiction where the defendant maintains its principal place of business
291 | and such litigation shall be governed by laws of that jurisdiction, without
292 | reference to its conflict-of-law provisions. Nothing in this Section shall
293 | prevent a party’s ability to bring cross-claims or counter-claims.
294 |
295 | 9. Miscellaneous
296 |
297 | This License represents the complete agreement concerning the subject matter
298 | hereof. If any provision of this License is held to be unenforceable, such
299 | provision shall be reformed only to the extent necessary to make it
300 | enforceable. Any law or regulation which provides that the language of a
301 | contract shall be construed against the drafter shall not be used to construe
302 | this License against a Contributor.
303 |
304 |
305 | 10. Versions of the License
306 |
307 | 10.1. New Versions
308 |
309 | Mozilla Foundation is the license steward. Except as provided in Section
310 | 10.3, no one other than the license steward has the right to modify or
311 | publish new versions of this License. Each version will be given a
312 | distinguishing version number.
313 |
314 | 10.2. Effect of New Versions
315 |
316 | You may distribute the Covered Software under the terms of the version of
317 | the License under which You originally received the Covered Software, or
318 | under the terms of any subsequent version published by the license
319 | steward.
320 |
321 | 10.3. Modified Versions
322 |
323 | If you create software not governed by this License, and you want to
324 | create a new license for such software, you may create and use a modified
325 | version of this License if you rename the license and remove any
326 | references to the name of the license steward (except to note that such
327 | modified license differs from this License).
328 |
329 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
330 | If You choose to distribute Source Code Form that is Incompatible With
331 | Secondary Licenses under the terms of this version of the License, the
332 | notice described in Exhibit B of this License must be attached.
333 |
334 | Exhibit A - Source Code Form License Notice
335 |
336 | This Source Code Form is subject to the
337 | terms of the Mozilla Public License, v.
338 | 2.0. If a copy of the MPL was not
339 | distributed with this file, You can
340 | obtain one at
341 | http://mozilla.org/MPL/2.0/.
342 |
343 | If it is not possible or desirable to put the notice in a particular file, then
344 | You may include the notice in a location (such as a LICENSE file in a relevant
345 | directory) where a recipient would be likely to look for such a notice.
346 |
347 | You may add additional accurate notices of copyright ownership.
348 |
349 | Exhibit B - “Incompatible With Secondary Licenses” Notice
350 |
351 | This Source Code Form is “Incompatible
352 | With Secondary Licenses”, as defined by
353 | the Mozilla Public License, v. 2.0.
354 |
355 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Joomlatools Console
4 | =====================
5 |
6 | [Joomlatools Console](https://www.joomlatools.com/developer/tools/console/) simplifies the management of Joomla sites. It is designed to work on Linux and MacOS. Windows users can use it in [Joomlatools Server](https://github.com/joomlatools/joomlatools-server).
7 |
8 | ## Requirements
9 |
10 | * PHP7.3 or newer
11 | * Linux, MacOS, or [Joomlatools Server](https://github.com/joomlatools/joomlatools-server)
12 | * Composer
13 | * Joomla versions 3.5 and 4.x
14 |
15 | ## Installation
16 |
17 | 1. Install using Composer:
18 |
19 | `$ composer global require joomlatools/console`
20 |
21 | 1. Tell your system where to find the executable by adding the composer directory to your PATH. Add the following line to your shell configuration file called either .profile, .bash_profile, .bash_aliases, or .bashrc. This file is located in your home folder.
22 |
23 | `$ export PATH="$PATH:~/.composer/vendor/bin"`
24 |
25 | For Ubuntu 19+ you may find you should use:
26 |
27 | `export PATH="$PATH:$HOME/.config/composer/vendor/bin"`
28 |
29 | 1. Verify the installation
30 |
31 | `$ joomla --version`
32 |
33 | 1. To create a new site with the latest Joomla version, run:
34 |
35 | ```shell
36 | joomla site:create testsite
37 | ```
38 |
39 | The newly installed site will be available at /var/www/testsite and testsite.test after that. The default Super User's name and password is set to: `admin` / `admin`.
40 |
41 | By default, the web server root is set to _/var/www_. You can pass _--www=/my/server/path_ to commands for custom values. You can choose the Joomla version or the sample data to be installed:
42 |
43 | ```shell
44 | joomla site:create testsite --release=4.0 --sample-data=blog
45 | ```
46 |
47 | 1. For other available options, run:
48 |
49 | `$ joomla --list`
50 |
51 | 1. Read our [documentation pages](https://www.joomlatools.com/developer/tools/console/) to learn more about using the tool.
52 |
53 | ## Development
54 |
55 | To setup the tool for development:
56 |
57 | 1. Clone the repository:
58 |
59 | ```
60 | git clone git@github.com:joomlatools/joomlatools-console.git
61 | ```
62 |
63 | 1. Fetch the dependencies:
64 |
65 | ```
66 | composer install
67 | ```
68 |
69 | 1. Now you can execute the tool with:
70 |
71 | ```
72 | bin/joomla list
73 | ```
74 |
75 | 1. Happy coding!
76 |
77 | ## Contributing
78 |
79 | Joomlatools Console is an open source, community-driven project. Contributions are welcome from everyone.
80 | We have [contributing guidelines](CONTRIBUTING.md) to help you get started.
81 |
82 | ## Contributors
83 |
84 | See the list of [contributors](https://github.com/joomlatools/joomlatools-console/contributors).
85 |
86 | ## License
87 |
88 | Joomlatools Console is free and open-source software licensed under the [MPLv2 license](LICENSE.txt).
89 |
90 | ## Community
91 |
92 | Keep track of development and community news.
93 |
94 | * Follow [@joomlatoolsdev on Twitter](https://twitter.com/joomlatoolsdev)
95 | * Join [joomlatools/dev on Gitter](http://gitter.im/joomlatools/dev)
96 | * Read the [Joomlatools Developer Blog](https://www.joomlatools.com/developer/blog/)
97 | * Subscribe to the [Joomlatools Developer Newsletter](https://www.joomlatools.com/developer/newsletter/)
98 |
99 | [Joomlatools Console]: https://www.joomlatools.com/developer/tools/console/
100 |
--------------------------------------------------------------------------------
/bin/.files/joomla2.users.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO `j_users` (`id`, `name`, `username`, `email`, `password`, `block`, `sendEmail`, `registerDate`, `lastvisitDate`, `activation`, `params`, `lastResetTime`, `resetCount`)
2 | VALUES
3 | (970, 'Super User', 'admin', 'admin@example.com', 'e8d876703ae04a3fe6c4868ff296fb9f:99SknoS4CFHGWhkOtk8cNTIDXR0bSUvN', 0, 1, NOW(), NOW(), '0', '', NOW(), 0),
4 | (971, 'User', 'user', 'user@example.com', '92b46d92fc58eb86e92a8c796febeb34:fXySsDEWvyiIg0ifkftgTkrXzmviMvC3', 0, 0, NOW(), NOW(), '', '{\"admin_style\":\"\",\"admin_language\":\"\",\"language\":\"\",\"editor\":\"\",\"helpsite\":\"\",\"timezone\":\"\"}', NOW(), 0),
5 | (972, 'Manager', 'manager', 'manager@example.com', '770b271ae81867018860e471d51781c9:8CqzT83QhW5AJACYBqDqoHJJE21l8r8Y', 0, 0, NOW(), NOW(), '', '{\"admin_style\":\"\",\"admin_language\":\"\",\"language\":\"\",\"editor\":\"\",\"helpsite\":\"\",\"timezone\":\"\"}', NOW(), 0);
6 |
7 | INSERT INTO `j_user_usergroup_map` (`user_id`, `group_id`)
8 | VALUES
9 | (970, 8),
10 | (971, 2),
11 | (972, 6);
12 |
--------------------------------------------------------------------------------
/bin/.files/joomla3.users.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO `j_users` (`id`, `name`, `username`, `email`, `password`, `block`, `sendEmail`, `registerDate`, `lastvisitDate`, `activation`, `params`, `lastResetTime`, `resetCount`)
2 | VALUES
3 | (951, 'Super User', 'admin', 'admin@example.com', '871b1a6d54b378b5547a945ea1a8bd18:3UgsAngDFq7D0FRmyiWey4qgV8n5PpEJ', 0, 1, NOW(), NOW(), '0', '{\"admin_style\":\"\",\"admin_language\":\"\",\"language\":\"\",\"editor\":\"\",\"helpsite\":\"\",\"timezone\":\"\"}', NOW(), 0),
4 | (952, 'User', 'user', 'user@example.com', '931d334de664be1135bed97fd9bb7b62:ZzvicSTnh9dr1Ln36G3MgkC9WSa9J4PW', 0, 0, NOW(), NOW(), '', '{\"admin_style\":\"\",\"admin_language\":\"\",\"language\":\"\",\"editor\":\"\",\"helpsite\":\"\",\"timezone\":\"\"}', NOW(), 0),
5 | (953, 'Manager', 'manager', 'manager@example.com', 'e0f025cc620a663e172c8b25911e5c4e:44wqdHQWhDPcrRg5koGsWJ9Zlhr9WC5x', 0, 0, NOW(), NOW(), '', '{\"admin_style\":\"\",\"admin_language\":\"\",\"language\":\"\",\"editor\":\"\",\"helpsite\":\"\",\"timezone\":\"\"}', NOW(), 0);
6 |
7 | INSERT INTO `j_user_usergroup_map` (`user_id`, `group_id`)
8 | VALUES
9 | (951, 8),
10 | (952, 2),
11 | (953, 6);
12 |
--------------------------------------------------------------------------------
/bin/.files/vhosts/apache.conf:
--------------------------------------------------------------------------------
1 |
2 | ServerAdmin webmaster@%site%.test
3 | DocumentRoot %root%
4 | ServerName %site%.test
5 | ServerAlias www.%site%.test %site%.dev www.%site%.dev
6 |
7 |
8 | Options Indexes FollowSymLinks
9 | AllowOverride All
10 | Require all granted
11 |
12 |
13 | ErrorLog /var/log/apache2/%site%.test_error.log
14 | CustomLog /var/log/apache2/%site%.test_access.log combined
15 |
16 |
--------------------------------------------------------------------------------
/bin/joomla:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | = 0; $i--)
17 | {
18 | $dir = implode(DIRECTORY_SEPARATOR, array_slice($dirs, 0, $i));
19 | $autoload = $dir . DIRECTORY_SEPARATOR . 'autoload.php';
20 | $vendored = $dir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
21 |
22 | if (file_exists($vendored))
23 | {
24 | require $vendored;
25 | break;
26 | }
27 | else if (file_exists($autoload))
28 | {
29 | require $autoload;
30 | break;
31 | }
32 | }
33 |
34 | $application = new Joomlatools\Console\Application();
35 | $application->run();
36 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "joomlatools/console",
3 | "type": "project",
4 | "license": "MPL-2.0",
5 | "description": "This command-line script helps to ease the management of Joomla sites in your development environment.",
6 | "keywords": ["joomla", "console", "tools"],
7 | "homepage": "https://github.com/joomlatools/joomlatools-console",
8 | "authors": [
9 | {
10 | "name": "Joomlatools",
11 | "email": "info@joomlatools.com",
12 | "homepage": "https://www.joomlatools.com"
13 | }
14 | ],
15 | "require": {
16 | "php": ">=7.3",
17 | "symfony/console": "^4.0|^5.0",
18 | "symfony/yaml": "^4.0"
19 | },
20 | "autoload": {
21 | "psr-0": {"Joomlatools\\": "src/"}
22 | },
23 | "bin": ["bin/joomla"]
24 | }
25 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joomlatools/joomlatools-console/7237a3478a57ad581175fedef74201c62048f846/screenshot.png
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Application.php:
--------------------------------------------------------------------------------
1 | _input = $input;
74 | $this->_output = $output;
75 |
76 | $this->configureIO($this->_input, $this->_output);
77 |
78 | $this->_setup();
79 |
80 | $this->_loadPlugins();
81 |
82 | $this->_loadExtraCommands();
83 |
84 | parent::run($this->_input, $this->_output);
85 | }
86 |
87 | /**
88 | * Get the home directory path
89 | *
90 | * @return string Path to the Joomlatools Console home directory
91 | */
92 | public function getConsoleHome()
93 | {
94 | $home = getenv('HOME');
95 | $customHome = getenv('JOOMLATOOLS_CONSOLE_HOME');
96 |
97 | if (!empty($customHome)) {
98 | $home = $customHome;
99 | }
100 |
101 | return rtrim($home, '/') . '/.joomlatools/console';
102 | }
103 |
104 | /**
105 | * Get the plugin path
106 | *
107 | * @return string Path to the plugins directory
108 | */
109 | public function getPluginPath()
110 | {
111 | if (empty($this->_plugin_path)) {
112 | $this->_plugin_path = $this->getConsoleHome() . '/plugins';
113 | }
114 |
115 | return $this->_plugin_path;
116 | }
117 |
118 | /**
119 | * Gets the default commands that should always be available.
120 | *
121 | * @return Command[] An array of default Command instances
122 | */
123 | protected function getDefaultCommands()
124 | {
125 | $commands = parent::getDefaultCommands();
126 |
127 | $commands = array_merge($commands, array(
128 | new Command\Database\Install(),
129 | new Command\Database\Drop(),
130 | new Command\Database\Export(),
131 |
132 | new Command\Extension\Install(),
133 | new Command\Extension\Register(),
134 | new Command\Extension\Symlink(),
135 |
136 | new Command\Plugin\ListAll(),
137 | new Command\Plugin\Install(),
138 | new Command\Plugin\Uninstall(),
139 |
140 | new Command\Site\Configure(),
141 | new Command\Site\Create(),
142 | new Command\Site\Delete(),
143 | new Command\Site\Download(),
144 | new Command\Site\Export(),
145 | new Command\Site\Install(),
146 | new Command\Site\Listing(),
147 |
148 | new Command\Vhost\Create(),
149 | new Command\Vhost\Remove(),
150 |
151 | new Command\Versions()
152 | ));
153 |
154 | return $commands;
155 | }
156 |
157 | /**
158 | * Get the list of installed plugin packages.
159 | *
160 | * @return array Array of package names as key and their version as value
161 | */
162 | public function getPlugins()
163 | {
164 | if (!$this->_plugins) {
165 |
166 | $manifest = $this->getPluginPath() . '/composer.json';
167 |
168 | if (!file_exists($manifest)) {
169 | return array();
170 | }
171 |
172 | $contents = file_get_contents($manifest);
173 |
174 | if ($contents === false) {
175 | return array();
176 | }
177 |
178 | $data = json_decode($contents);
179 |
180 | if (!isset($data->require)) {
181 | return array();
182 | }
183 |
184 | $this->_plugins = array();
185 |
186 | foreach ($data->require as $package => $version)
187 | {
188 | $file = $this->getPluginPath() . '/vendor/' . $package . '/composer.json';
189 |
190 | if (file_exists($file))
191 | {
192 | $json = file_get_contents($file);
193 | $manifest = json_decode($json);
194 |
195 | if (is_null($manifest)) {
196 | continue;
197 | }
198 |
199 | if (isset($manifest->type) && $manifest->type == 'joomlatools-console-plugin') {
200 | $this->_plugins[$package] = $version;
201 | }
202 | }
203 | }
204 | }
205 |
206 | return $this->_plugins;
207 | }
208 |
209 | /**
210 | * Loads extra commands from the ~/.joomlatools/console/commands/ folder
211 | *
212 | * Each PHP file in the folder is included and if the class in the file extends the base Symfony command
213 | * it's instantiated and added to the app.
214 | *
215 | * @return void
216 | */
217 | protected function _loadExtraCommands()
218 | {
219 | $path = $this->getConsoleHome().'/commands';
220 |
221 | if (\is_dir($path))
222 | {
223 | $iterator = new \DirectoryIterator($path);
224 |
225 | foreach ($iterator as $file)
226 | {
227 | if ($file->getExtension() == 'php')
228 | {
229 | require $file->getPathname();
230 |
231 | $className = $file->getBasename('.php');
232 |
233 | if (\class_exists($className))
234 | {
235 | $reflection = new \ReflectionClass($className);
236 |
237 | if (!$reflection->isSubclassOf('\Symfony\Component\Console\Command\Command')) {
238 | continue;
239 | }
240 |
241 | $command = new $className();
242 |
243 | if (!$command instanceof \Symfony\Component\Console\Command\Command) {
244 | continue;
245 | }
246 |
247 | $this->add($command);
248 | }
249 | }
250 | }
251 | }
252 | }
253 |
254 | /**
255 | * Set up environment
256 | */
257 | protected function _setup()
258 | {
259 | $home = $this->getConsoleHome();
260 |
261 | if (!file_exists($home))
262 | {
263 | $result = @mkdir($home, 0775, true);
264 |
265 | if (!$result) {
266 | $this->_output->writeln(sprintf('Unable to create home directory: %s. Please check write permissions.', $home));
267 | }
268 | }
269 |
270 | // Handle legacy plugin directory
271 | if (is_writable($home) && !file_exists($this->getPluginPath()))
272 | {
273 | $old = realpath(dirname(__FILE__) . '/../../../plugins/');
274 |
275 | if (file_exists($old))
276 | {
277 | $this->_output->writeln('Moving legacy plugin directory to ~/.joomlatools/console/plugins.');
278 |
279 | $cmd = sprintf('mv %s %s', escapeshellarg($old), escapeshellarg($this->getPluginPath()));
280 | exec($cmd);
281 | }
282 | }
283 | }
284 |
285 | /**
286 | * Loads plugins into the application.
287 | */
288 | protected function _loadPlugins()
289 | {
290 | $autoloader = $this->getPluginPath() . '/vendor/autoload.php';
291 |
292 | if (file_exists($autoloader)) {
293 | require_once $autoloader;
294 | }
295 |
296 | $plugins = $this->getPlugins();
297 |
298 | $classes = array();
299 | foreach ($plugins as $package => $version)
300 | {
301 | $path = $this->getPluginPath() . '/vendor/' . $package;
302 | $directories = glob($path.'/*/Console/Command', GLOB_ONLYDIR);
303 |
304 | foreach ($directories as $directory)
305 | {
306 | $vendor = substr($directory, strlen($path) + 1, strlen('/Console/Command') * -1);
307 | $iterator = new \DirectoryIterator($directory);
308 |
309 | foreach ($iterator as $file)
310 | {
311 | if ($file->getExtension() == 'php') {
312 | $classes[] = sprintf('%s\Console\Command\%s', $vendor, $file->getBasename('.php'));
313 | }
314 | }
315 | }
316 | }
317 |
318 | foreach ($classes as $class)
319 | {
320 | if (class_exists($class))
321 | {
322 | $command = new $class();
323 |
324 | if (!$command instanceof \Symfony\Component\Console\Command\Command) {
325 | continue;
326 | }
327 |
328 | $name = $command->getName();
329 |
330 | if(!$this->has($name)) {
331 | $this->add($command);
332 | }
333 | else $this->_output->writeln("Notice: The '$class' command wants to register the '$name' command but it already exists, ignoring.");
334 | }
335 | }
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Configurable.php:
--------------------------------------------------------------------------------
1 | _getConfigOverride($name) ?? $default;
23 | }
24 |
25 | return parent::addArgument($name, $mode, $description, $default);
26 | }
27 |
28 | public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
29 | {
30 | if ($mode != InputOption::VALUE_NONE) {
31 | $default = $this->_getConfigOverride($name) ?? $default;
32 | }
33 |
34 | return parent::addOption($name, $shortcut, $mode, $description, $default);
35 | }
36 |
37 | protected function _getConfigOverride($name)
38 | {
39 | $override = null;
40 |
41 | if (is_null($this->_config))
42 | {
43 | $file = sprintf('%s/.joomlatools/console/config.yaml', trim(`echo ~`));
44 |
45 | if (file_exists($file))
46 | {
47 | $file_contents = \file_get_contents($file);
48 | $env = $_ENV;
49 |
50 | // Replace longest keys first
51 | uksort($env, function($a, $b){
52 | return strlen($b) - strlen($a);
53 | });
54 |
55 | // Replace environment variables in the file
56 | foreach ($env as $key => $value) {
57 | $file_contents = \str_replace('$'.$key, $value, $file_contents);
58 | }
59 |
60 | $this->_config = Yaml::parse($file_contents);
61 | } else {
62 | $this->_config = false;
63 | }
64 | }
65 |
66 | $config = $this->_config;
67 |
68 | if (is_array($config))
69 | {
70 | if ($command = $this->getName())
71 | {
72 | if (isset($config[$command][$name])) {
73 | $override = $config[$command][$name];
74 | }
75 | }
76 |
77 | // Look for global settings
78 |
79 | if (is_null($override) && isset($config['globals'][$name])) {
80 | $override = $config['globals'][$name];
81 | }
82 | }
83 |
84 | return $override;
85 | }
86 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Database/AbstractDatabase.php:
--------------------------------------------------------------------------------
1 | addOption(
28 | 'mysql-login',
29 | 'L',
30 | InputOption::VALUE_REQUIRED,
31 | "MySQL credentials in the form of user:password",
32 | 'root:root'
33 | )
34 | ->addOption(
35 | 'mysql-host',
36 | 'H',
37 | InputOption::VALUE_REQUIRED,
38 | "MySQL host",
39 | 'localhost'
40 | )
41 | ->addOption(
42 | 'mysql-port',
43 | 'P',
44 | InputOption::VALUE_REQUIRED,
45 | "MySQL port",
46 | 3306
47 | )
48 | ->addOption(
49 | 'mysql-db-prefix',
50 | null,
51 | InputOption::VALUE_REQUIRED,
52 | sprintf("MySQL database name prefix. Defaults to `%s`", $this->target_db_prefix),
53 | $this->target_db_prefix
54 | )
55 | ->addOption(
56 | 'mysql-database',
57 | 'db',
58 | InputOption::VALUE_REQUIRED,
59 | "MySQL database name. If set, the --mysql-db-prefix option will be ignored."
60 | )
61 | ->addOption(
62 | 'mysql-driver',
63 | null,
64 | InputOption::VALUE_REQUIRED,
65 | "MySQL driver",
66 | 'mysqli'
67 | )
68 | ;
69 | }
70 |
71 | protected function execute(InputInterface $input, OutputInterface $output)
72 | {
73 | parent::execute($input, $output);
74 |
75 | $db_name = $input->getOption('mysql-database');
76 | if (empty($db_name))
77 | {
78 | $this->target_db_prefix = $input->getOption('mysql-db-prefix');
79 | $this->target_db = $this->target_db_prefix.$this->site;
80 | }
81 | else
82 | {
83 | $this->target_db_prefix = '';
84 | $this->target_db = $db_name;
85 | }
86 |
87 | $credentials = explode(':', $input->getOption('mysql-login'), 2);
88 |
89 | $this->mysql = (object) array(
90 | 'user' => $credentials[0],
91 | 'password' => $credentials[1],
92 | 'host' => $input->getOption('mysql-host'),
93 | 'port' => (int) $input->getOption('mysql-port'),
94 | 'driver' => strtolower($input->getOption('mysql-driver'))
95 | );
96 |
97 | if (!in_array($this->mysql->driver, array('mysql', 'mysqli'))) {
98 | throw new \RuntimeException(sprintf('Invalid MySQL driver %s', $this->mysql->driver));
99 | }
100 |
101 | return 0;
102 | }
103 |
104 | protected function _backupDatabase($target_file)
105 | {
106 | $this->_executeMysqldump(sprintf("--skip-dump-date --skip-extended-insert --no-tablespaces %s > %s", $this->target_db, $target_file));
107 | }
108 |
109 | protected function _executePDO($query, $database = null) {
110 | $database = $database ?: $this->target_db;
111 | $connectionString = "mysql:host={$this->mysql->host}:{$this->mysql->port};dbname={$database};charset=utf8mb4";
112 | $pdoDB = new \PDO($connectionString, $this->mysql->user, $this->mysql->password);
113 | $pdoDB->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
114 |
115 | return $pdoDB->query($query);
116 | }
117 |
118 | protected function _executeSQL($query, $database = '')
119 | {
120 | return $this->_executeMysqlWithCredentials(function($path) use($query, $database) {
121 | return "echo '$query' | mysql --defaults-extra-file=$path $database";
122 | });
123 | }
124 |
125 | protected function _executeMysql($command)
126 | {
127 | return $this->_executeMysqlWithCredentials(function($path) use($command) {
128 | return "mysql --defaults-extra-file=$path $command";
129 | });
130 | }
131 |
132 | protected function _executeMysqldump($command)
133 | {
134 | return $this->_executeMysqlWithCredentials(function($path) use($command) {
135 | return "mysqldump --defaults-extra-file=$path $command";
136 | });
137 | }
138 |
139 | /**
140 | * Write a temporary --defaults-extra-file file and execute a Mysql command given from the callback
141 | *
142 | * @param callable $callback Receives a single string with the path to the --defaults-extra-file path
143 | * @return void
144 | */
145 | private function _executeMysqlWithCredentials(callable $callback)
146 | {
147 | try {
148 | $file = tmpfile();
149 | $path = stream_get_meta_data($file)['uri'];
150 |
151 | $contents = <<mysql->user}
154 | password={$this->mysql->password}
155 | host={$this->mysql->host}
156 | port={$this->mysql->port}
157 | STR;
158 |
159 | fwrite($file, $contents);
160 |
161 |
162 | return exec($callback($path));
163 | }
164 | finally {
165 | if (\is_resource($file)) {
166 | \fclose($file);
167 | }
168 | }
169 | }
170 |
171 | protected function _promptDatabaseDetails(InputInterface $input, OutputInterface $output)
172 | {
173 | $this->mysql->user = $this->_ask($input, $output, 'MySQL user', $this->mysql->user, true);
174 | $this->mysql->password = $this->_ask($input, $output, 'MySQL password', $this->mysql->password, true, true);
175 | $this->mysql->host = $this->_ask($input, $output, 'MySQL host', $this->mysql->host, true);
176 | $this->mysql->port = (int) $this->_ask($input, $output, 'MySQL port', $this->mysql->port, true);
177 | $this->mysql->driver = $this->_ask($input, $output, 'MySQL driver', array('mysqli', 'mysql'), true);
178 |
179 | $output->writeln('Choose the database name. We will attempt to create it if it does not exist.');
180 | $this->target_db = $this->_ask($input, $output, 'MySQL database', $this->target_db, true);
181 |
182 | $input->setOption('mysql-login', $this->mysql->user . ':' . $this->mysql->password);
183 | $input->setOption('mysql-host', $this->mysql->host);
184 | $input->setOption('mysql-port', $this->mysql->port);
185 | $input->setOption('mysql-database', $this->target_db);
186 | $input->setOption('mysql-driver', $this->mysql->driver);
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Database/Drop.php:
--------------------------------------------------------------------------------
1 | setName('database:drop')
22 | ->setDescription('Drop the site\'s database');
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output)
26 | {
27 | parent::execute($input, $output);
28 |
29 | $result = $this->_executeSQL(sprintf("DROP DATABASE IF EXISTS `%s`", $this->target_db));
30 |
31 | if (!empty($result)) {
32 | throw new \RuntimeException(sprintf('Cannot drop database %s. Error: %s', $this->target_db, $result));
33 | }
34 |
35 | return 0;
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Database/Export.php:
--------------------------------------------------------------------------------
1 |
7 | * @link http://github.com/joomlatools/joomlatools-console-backup for the canonical source repository
8 | */
9 |
10 | namespace Joomlatools\Console\Command\Database;
11 |
12 | use Symfony\Component\Console\Input\InputInterface;
13 | use Symfony\Component\Console\Input\InputOption;
14 | use Symfony\Component\Console\Output\OutputInterface;
15 |
16 |
17 | /**
18 | * Backup plugin class.
19 | *
20 | * @author Steven Rombauts
21 | * @package Joomlatools\Console
22 | */
23 | class Export extends AbstractDatabase
24 | {
25 | protected function configure()
26 | {
27 | parent::configure();
28 |
29 | $this->setName('database:export')
30 | ->addOption(
31 | 'folder',
32 | null,
33 | InputOption::VALUE_REQUIRED,
34 | "Target folder where the backup should be stored. Defaults to site folder",
35 | null
36 | )
37 | ->addOption(
38 | 'filename',
39 | null,
40 | InputOption::VALUE_REQUIRED,
41 | "File name for the backup. Defaults to sitename_date.format",
42 | null
43 | )
44 | ->addOption(
45 | 'per-table',
46 | null,
47 | InputOption::VALUE_NONE,
48 | "If set, each table will be exported into a separate file",
49 | )
50 | ->setDescription('Export the database of a site');
51 | }
52 |
53 | protected function execute(InputInterface $input, OutputInterface $output)
54 | {
55 | parent::execute($input, $output);
56 |
57 | $this->check();
58 |
59 | $folder = $input->getOption('folder') ?? $this->target_dir;
60 |
61 | if (!\is_dir($folder)) {
62 | @mkdir($folder, 0755, true);
63 |
64 | if (!\is_dir($folder)) {
65 | throw new \RuntimeException("Folder $folder doesn't exist.");
66 | }
67 | }
68 |
69 | if ($input->getOption('per-table'))
70 | {
71 | $statement = $this->_executePDO('show tables');
72 |
73 | while (($table = $statement->fetchColumn()) !== false) {
74 |
75 | $this->_executeMysqldump(sprintf("--skip-dump-date --skip-comments --skip-extended-insert --no-tablespaces %s %s > %s", $this->target_db, $table, $folder.'/'.$table.'.sql'));
76 | }
77 |
78 | } else {
79 | $path = $folder.'/'.($input->getOption('filename') ?? $this->site.'_database_'.date('Y-m-d').'.sql');
80 |
81 | $this->_backupDatabase($path);
82 | }
83 |
84 | return 0;
85 | }
86 |
87 | public function check()
88 | {
89 | if (!file_exists($this->target_dir)) {
90 | throw new \RuntimeException(sprintf('The site %s does not exist', $this->site));
91 | }
92 |
93 | $result = $this->_executeSQL(sprintf("SHOW DATABASES LIKE \"%s\"", $this->target_db));
94 |
95 | if (empty($result)) {
96 | throw new \RuntimeException(sprintf('Database %s does not exist', $this->target_db));
97 | }
98 |
99 | }
100 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Database/Install.php:
--------------------------------------------------------------------------------
1 | setName('database:install')
52 | ->setDescription('Install the Joomla database')
53 | ->addOption(
54 | 'sample-data',
55 | null,
56 | InputOption::VALUE_REQUIRED,
57 | 'Sample data to install (default|blog|brochure|learn|testing). Ignored if custom dump files are given using --sql-dumps.'
58 | )
59 | ->addOption(
60 | 'sql-dumps',
61 | null,
62 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
63 | 'Full path to SQL dump file to import. If not set, the command will use the default Joomla installation files.',
64 | array()
65 | )
66 | ->addOption(
67 | 'drop',
68 | 'd',
69 | InputOption::VALUE_NONE,
70 | 'Drop database if it already exists'
71 | )
72 | ->addOption(
73 | 'skip-exists-check',
74 | 'e',
75 | InputOption::VALUE_NONE,
76 | 'Do not check if database already exists or not.'
77 | )
78 | ->addOption(
79 | 'skip-create-statement',
80 | null,
81 | InputOption::VALUE_NONE,
82 | 'Do not run the "CREATE IF NOT EXISTS " query. Use this if the user does not have CREATE privileges on the database.'
83 | )
84 | ;
85 | }
86 |
87 | protected function execute(InputInterface $input, OutputInterface $output)
88 | {
89 | parent::execute($input, $output);
90 |
91 | $this->drop = $input->getOption('drop');
92 | $this->skip_check = $input->getOption('skip-exists-check');
93 | $this->skip_create_statement = $input->getOption('skip-create-statement');
94 |
95 | $this->check($input, $output);
96 |
97 | if ($this->drop) {
98 | $this->_executeSQL(sprintf("DROP DATABASE IF EXISTS `%s`", $this->target_db));
99 | }
100 |
101 | if (!$this->skip_create_statement)
102 | {
103 | $result = $this->_executeSQL(sprintf("CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8", $this->target_db));
104 |
105 | if (!empty($result)) {
106 | throw new \RuntimeException(sprintf('Cannot create database %s. Error: %s', $this->target_db, $result));
107 | }
108 | }
109 |
110 | $imports = $this->_getSQLFiles($input, $output);
111 |
112 | foreach($imports as $import)
113 | {
114 | $tmp = tempnam('/tmp', 'dump');
115 | $contents = file_get_contents($import);
116 | $contents = str_replace('#__', 'j_', $contents);
117 |
118 | file_put_contents($tmp, $contents);
119 |
120 | $result = $this->_executeMysql(sprintf("%s < %s", $this->target_db, $tmp));
121 |
122 | unlink($tmp);
123 |
124 | if (!empty($result)) {
125 | throw new \RuntimeException(sprintf('Cannot import database "%s". Error: %s', basename($import), $result));
126 | }
127 | }
128 |
129 | // Fix the #__schemas table for Joomla CMS 2.5+
130 | $path = $this->target_dir . '/administrator/components/com_admin/sql/updates/mysql/';
131 | if (is_dir($path))
132 | {
133 | $updates = glob("$path/*.sql");
134 |
135 | if (count($updates))
136 | {
137 | natsort($updates);
138 |
139 | $schema = substr(basename(array_pop($updates)), 0, -4);
140 | $schema = preg_replace('/[^a-z0-9\.\-\_]/i', '', $schema);
141 |
142 | $version = Util::getJoomlaVersion($this->target_dir);
143 |
144 | $executeQuery = function($sql) {
145 | $this->_executeMysql(sprintf("%s -e %s", $this->target_db, escapeshellarg($sql)));
146 | };
147 |
148 | $executeQuery("REPLACE INTO j_schemas (extension_id, version_id) VALUES (700, '$schema');");
149 | $executeQuery("UPDATE j_extensions SET manifest_cache = '{\"version\": \"$version->release\"}' WHERE manifest_cache = '';");
150 | }
151 | }
152 |
153 | return 0;
154 | }
155 |
156 | public function check(InputInterface $input, OutputInterface $output)
157 | {
158 | if (!file_exists($this->target_dir)) {
159 | throw new \RuntimeException(sprintf('Site %s not found', $this->site));
160 | }
161 |
162 | if ($this->drop === false && !($this->skip_check === true || $this->skip_create_statement === true ))
163 | {
164 | $result = $this->_executeSQL(sprintf("SHOW DATABASES LIKE \"%s\"", $this->target_db));
165 |
166 | if (!empty($result)) {
167 | throw new \RuntimeException(sprintf('A database with name %s already exists', $this->target_db));
168 | }
169 | }
170 |
171 | $sample_data = $input->getOption('sample-data');
172 | if ($sample_data)
173 | {
174 | if (!in_array($sample_data, array('default', 'blog', 'brochure', 'testing', 'learn'))) {
175 | throw new \RuntimeException(sprintf('Unknown sample data "%s"', $this->sample_data));
176 | }
177 |
178 | $version = Util::getJoomlaVersion($this->target_dir);
179 |
180 | if($version !== false && $version->release)
181 | {
182 | if (in_array($sample_data, array('testing', 'learn')) && version_compare($version->release, '3.0.0', '<')) {
183 | throw new \RuntimeException(sprintf('%s does not support sample data %s', $version->release, $sample_data));
184 | }
185 | }
186 | }
187 | }
188 |
189 | protected function _getSQLFiles(InputInterface $input, OutputInterface $output)
190 | {
191 | $dumps = $input->getOption('sql-dumps');
192 |
193 | if (count($dumps) > 0)
194 | {
195 | foreach ($dumps as $dump)
196 | {
197 | if (!file_exists($dump)) {
198 | throw new \RuntimeException(sprintf('Can not find SQL dump file %s', $dump));
199 | }
200 | }
201 |
202 | return $dumps;
203 | }
204 |
205 | $version = Util::getJoomlaVersion($this->target_dir);
206 | $imports = $this->_getInstallFiles($input->getOption('sample-data'));
207 |
208 | if ($version !== false)
209 | {
210 | $users = 'joomla3.users.sql';
211 | if(is_numeric(substr($version->release, 0, 1)) && version_compare($version->release, '3.0.0', '<')) {
212 | $users = 'joomla2.users.sql';
213 | }
214 | $path = Util::getTemplatePath();
215 |
216 | $imports[] = $path.'/'.$users;
217 | }
218 |
219 | foreach ($imports as $import)
220 | {
221 | if (!file_exists($import)) {
222 | throw new \RuntimeException(sprintf('Can not find SQL dump file %s', $import));
223 | }
224 | }
225 |
226 | return $imports;
227 | }
228 |
229 | protected function _getInstallFiles($sample_data = false)
230 | {
231 | $files = array();
232 |
233 | $path = $this->target_dir.'/_installation/sql/mysql/';
234 |
235 | if (!file_exists($path)) {
236 | $path = $this->target_dir.'/installation/sql/mysql/';
237 | }
238 |
239 | $version = Util::getJoomlaVersion($this->target_dir);
240 |
241 | if (version_compare($version->release, '4.0.0-alpha12', '>') &&
242 | file_exists($path . "base.sql"))
243 | {
244 | $files[] = $path . "base.sql";
245 | $files[] = $path . "extensions.sql";
246 | $files[] = $path . "supports.sql";
247 |
248 | }else{
249 | $files[] = $path.'joomla.sql';
250 | }
251 |
252 | if ($sample_data)
253 | {
254 | $type = $sample_data == 'default' ? 'data' : $sample_data;
255 | $sample_db = $path . 'sample_' . $type . '.sql';
256 |
257 | if (file_exists($sample_db)){
258 | $files[] = $sample_db;
259 | }
260 | }
261 |
262 | return $files;
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Extension/AbstractExtension.php:
--------------------------------------------------------------------------------
1 | 'component',
29 | 'mod_' => 'module',
30 | 'plg_' => 'plugin',
31 | 'pkg_' => 'package',
32 | 'lib_' => 'library',
33 | 'tpl_' => 'template',
34 | 'lng_' => 'language'
35 | );
36 |
37 | protected $exceptions = array(
38 | 'module' => array(
39 | 'require' => array(
40 | 'model' => '/components/com_modules/models/module.php'
41 | ),
42 | 'model' => '\\ModulesModelModule',
43 | 'table' => array(
44 | 'type' => 'module',
45 | 'prefix' => 'JTable'
46 | ),
47 | ),
48 | 'template' => array(
49 | 'require' => array(
50 | 'model' => '/components/com_templates/models/style.php',
51 | 'table' => '/components/com_templates/tables/style.php'
52 | ),
53 | 'model' => 'TemplatesModelStyle',
54 | 'table' => array(
55 | 'type' => 'Style',
56 | 'prefix' => 'TemplatesTable'
57 | ),
58 | ));
59 |
60 | protected function configure()
61 | {
62 | $this->addArgument(
63 | 'site',
64 | InputArgument::REQUIRED,
65 | 'Alphanumeric site name. Also used in the site URL with .test domain'
66 | )->addArgument(
67 | 'extension',
68 | InputArgument::REQUIRED,
69 | 'Extension name'
70 | )->addOption(
71 | 'www',
72 | null,
73 | InputOption::VALUE_REQUIRED,
74 | "Web server root",
75 | '/var/www'
76 | );
77 | }
78 |
79 | protected function execute(InputInterface $input, OutputInterface $output)
80 | {
81 | $this->site = $input->getArgument('site');
82 | $this->www = $input->getOption('www');
83 | $this->target_dir = $this->www.'/'.$this->site;
84 | $this->extension = $input->getArgument('extension');
85 |
86 | return 0;
87 | }
88 |
89 | protected function check(InputInterface $input, OutputInterface $output)
90 | {
91 | if (!file_exists($this->target_dir)) {
92 | throw new \RuntimeException(sprintf('Site not found: %s', $this->site));
93 | }
94 | }
95 |
96 | protected function toggle($enable = false)
97 | {
98 | Bootstrapper::getApplication($this->target_dir);
99 |
100 | $dbo = \JFactory::getDbo();
101 | $query = \JFactory::getDbo()->getQuery(true)
102 | ->select('extension_id')
103 | ->from('#__extensions')
104 | ->where($dbo->quoteName('element') ." = " . $dbo->quote($this->extension));
105 |
106 | $dbo->setQuery($query);
107 | $extension = $dbo->loadResult('extension_id');
108 |
109 | if (!$extension) {
110 | throw new \RuntimeException("$this->extension does not exist");
111 | }
112 |
113 | $table = \JTable::getInstance('Extension');
114 | $table->load($extension);
115 |
116 | if ($table->protected == 1) {
117 | throw new \RuntimeException("Cannot disable core component $this->extension");
118 | }
119 |
120 | $table->enabled = (int) $enable;
121 |
122 | if (!$table->store()) {
123 | throw new \RuntimeException("Failed to update row: " . $table->getError());
124 | }
125 | }
126 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Extension/Install.php:
--------------------------------------------------------------------------------
1 | setName('extension:install')
29 | ->setDescription('Install extensions into a site using the discover method')
30 | ->setHelp(<<joomla extension:install testsite com_foobar
35 |
36 | The extension argument should match the element name (com_foobar) as defined in your extension XML manifest.
37 |
38 | For more information about Joomla's discover method, refer to the official documentation: https://docs.joomla.org/Help34:Extensions_Extension_Manager_Discover
39 |
40 | Alternatively simply pass in the Composer packages you would like to install: provide these in the format (vendor/package:[commit || [operator version])
41 | EOL
42 | )
43 | ->addArgument(
44 | 'extension',
45 | InputArgument::REQUIRED | InputArgument::IS_ARRAY,
46 | 'A list of extensions to install to the site using discover install. Use \'all\' to install all discovered extensions.'
47 | );
48 | }
49 |
50 | protected function execute(InputInterface $input, OutputInterface $output)
51 | {
52 | parent::execute($input, $output);
53 |
54 | $extensions = (array) $input->getArgument('extension');
55 |
56 | $this->composer_extensions = array_filter(array_map(function($x){
57 | if(strpos($x, '/') !== false){
58 | return $x;
59 | }
60 | }, $extensions));
61 |
62 | if(count($this->composer_extensions)) {
63 | $extensions = array_diff($extensions, $this->composer_extensions);
64 | }
65 |
66 | $this->extensions = $extensions;
67 |
68 | $this->check($input, $output);
69 |
70 | if (count($this->extensions)) {
71 | $this->installFiles($input, $output);
72 | }
73 |
74 | if (count($this->composer_extensions)) {
75 | $this->installPackages($input, $output);
76 | }
77 |
78 | return 0;
79 | }
80 |
81 | public function check(InputInterface $input, OutputInterface $output)
82 | {
83 | if (!file_exists($this->target_dir)) {
84 | throw new \RuntimeException(sprintf('Site not found: %s', $this->site));
85 | }
86 |
87 | if (count($this->composer_extensions))
88 | {
89 | $result = shell_exec('composer -v > /dev/null 2>&1 || { echo "false"; }');
90 |
91 | if (trim($result) == 'false')
92 | {
93 | $output->writeln('You need Composer installed globally. See: https://getcomposer.org/doc/00-intro.md#globally');
94 | exit();
95 | }
96 | }
97 | }
98 |
99 | public function installPackages(InputInterface $input, OutputInterface $output)
100 | {
101 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
102 | $output->writeln("Installing Composer packages ..");
103 | }
104 |
105 | $packages = array_map('escapeshellarg', $this->composer_extensions);
106 | $command = sprintf('composer --no-interaction --no-progress require %s --working-dir=%s', implode(' ', $packages), escapeshellarg($this->target_dir));
107 |
108 | passthru($command, $result);
109 |
110 | if ((int) $result) {
111 | throw new \RuntimeException('Failed to install Composer packages');
112 | }
113 | }
114 |
115 | public function installFiles(InputInterface $input, OutputInterface $output)
116 | {
117 | if (Util::isJoomla4($this->target_dir)) {
118 | Util::executeJ4CliCommand($this->target_dir, 'extension:discover');
119 | $result = Util::executeJ4CliCommand($this->target_dir, 'extension:discover:list | less');
120 | $verbosity = $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE ? '-vvv' : '';
121 |
122 | $results = [];
123 | foreach (explode("\n", $result) as $row) {
124 | if (!\preg_match('#[0-9]+#', $row)) continue; # skip table headers etc
125 |
126 | $data = preg_split('#\s+#', trim($row));
127 |
128 | $results[$data[0]] = $data[1];
129 | }
130 |
131 | if (isset($results['plg_system_joomlatools']) && (\in_array('all', $this->extensions) || \in_array('joomlatools-framework', $this->extensions))) {
132 | $result = Util::executeJ4CliCommand($this->target_dir, "extension:discover:install $verbosity --eid={$results['plg_system_joomlatools']}");
133 |
134 | unset($results['plg_system_joomlatools']);
135 |
136 | $output->writeln("Joomlatools Framework install: $result\n");
137 | }
138 |
139 | foreach ($results as $extension => $extension_id) {
140 | if (\in_array('all', $this->extensions) || \in_array(substr($extension, 4), $this->extensions) || \in_array($extension, $this->extensions)) {
141 | $result = Util::executeJ4CliCommand($this->target_dir, "extension:discover:install $verbosity --eid=$extension_id", );
142 |
143 | $output->writeln("$result\n");
144 | }
145 | }
146 |
147 | return;
148 | }
149 |
150 | $app = Bootstrapper::getApplication($this->target_dir);
151 | $db = \JFactory::getDbo();
152 |
153 | // Output buffer is used as a guard against Joomla including ._ files when searching for adapters
154 | // See: http://kadin.sdf-us.org/weblog/technology/software/deleting-dot-underscore-files.html
155 | ob_start();
156 |
157 | $installer = $app->getInstaller();
158 | $installer->discover();
159 |
160 | require_once JPATH_ADMINISTRATOR . '/components/com_installer/models/discover.php';
161 |
162 | $model = new \InstallerModelDiscover();
163 | $model->discover();
164 |
165 | // Trigger the populateState() method in the model
166 | $model->getState('list.limit');
167 | // then override the default limit to 999
168 | $model->setState('list.limit', 999);
169 |
170 | $results = $model->getItems();
171 |
172 | ob_end_clean();
173 |
174 | $install = array();
175 | $plugins = array();
176 |
177 | foreach ($this->extensions as $extension)
178 | {
179 | foreach ($results as $result)
180 | {
181 | $included = false;
182 |
183 | if (($result->element === 'joomlatools' && $result->type === 'plugin' && $result->folder === 'system')
184 | && ($extension == 'all' || $extension == 'joomlatools-framework' || $extension == $result->element)
185 | )
186 | {
187 | array_unshift($install, $result);
188 | $included = true;
189 | }
190 | elseif ($extension == 'all' || in_array($extension, array($result->element, substr($result->element, 4))))
191 | {
192 | $install[] = $result;
193 | $included = true;
194 | }
195 |
196 | if ($result->type == 'plugin' && $included) {
197 | $plugins[] = $result->extension_id;
198 | }
199 |
200 | if ($included && $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
201 | $output->writeln("Queued $result->name for installation.");
202 | }
203 | }
204 | }
205 |
206 | foreach ($install as $extension)
207 | {
208 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
209 | $output->writeln("Installing $extension->element ..");
210 | }
211 |
212 | try {
213 | $installer->discover_install($extension->extension_id);
214 | }
215 | catch (\Exception $e) {
216 | $output->writeln("Caught exception whilst installing $extension->type $extension->element: " . $e->getMessage() . "\n");
217 | }
218 |
219 | if (in_array($extension->extension_id, $plugins))
220 | {
221 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
222 | $output->writeln("Enabling plugin `$extension->element` (ID #$extension->extension_id) ..");
223 | }
224 |
225 | $sql = "UPDATE `#__extensions` SET `enabled` = 1 WHERE `extension_id` = '$extension->extension_id'";
226 |
227 | $db->setQuery($sql);
228 | $db->execute();
229 |
230 | if ($extension->element === 'joomlatools' && $extension->type === 'plugin' && $extension->folder === 'system')
231 | {
232 | $path = JPATH_PLUGINS . '/system/joomlatools/joomlatools.php';
233 |
234 | if (!file_exists($path)) {
235 | return;
236 | }
237 |
238 | require_once $path;
239 |
240 | if (class_exists('\PlgSystemJoomlatools'))
241 | {
242 | $dispatcher = \JEventDispatcher::getInstance();
243 | new \PlgSystemJoomlatools($dispatcher, (array)\JPLuginHelper::getPLugin('system', 'joomlatools'));
244 | }
245 |
246 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
247 | $output->writeln("Initialised new PlgSystemJoomlatools instance");
248 | }
249 | }
250 | }
251 | }
252 | }
253 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Extension/Iterator/Iterator.php:
--------------------------------------------------------------------------------
1 | source = $source;
28 | $this->target = $target;
29 |
30 | parent::__construct(new \RecursiveDirectoryIterator($source));
31 | }
32 |
33 | public function callHasChildren()
34 | {
35 | $filename = $this->getFilename();
36 | if ($filename[0] == '.') {
37 | return false;
38 | }
39 |
40 | $source = $this->key();
41 |
42 | $target = str_replace($this->source, '', $source);
43 | $target = str_replace('/site', '', $target);
44 | $target = Util::buildTargetPath($target, $this->target);
45 |
46 | if (is_link($target)) {
47 | unlink($target);
48 | }
49 |
50 | if (!is_dir($target))
51 | {
52 | $this->createLink($source, $target);
53 | return false;
54 | }
55 |
56 | return parent::callHasChildren();
57 | }
58 |
59 | public function createLink($source, $target)
60 | {
61 | if (!file_exists($target))
62 | {
63 | $source = Symlink::buildSymlinkPath($source, $target);
64 |
65 | if (!is_null($this->_output) && $this->_output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
66 | $this->_output->writeln(" * creating link: `$target` -> `$source`");
67 | }
68 |
69 | `ln -sf $source $target`;
70 | }
71 | }
72 |
73 | public function setOutput(OutputInterface $output)
74 | {
75 | $this->_output = $output;
76 | }
77 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Extension/Register.php:
--------------------------------------------------------------------------------
1 | setName('extension:register')
33 | ->setDescription('Register an extension with the `#__extensions` table.')
34 | ->setHelp(<<joomla extension:register testsite com_foobar
38 |
39 | The type of extension that gets registered is based on the first 4 characters of the extension argument you pass in. Example:
40 |
41 | * com_ => component
42 | * mod_ => module
43 | * plg_ => plugin (the plg_ will get stripped from the element field)
44 | * lib_ => library
45 | * pkg_ => package
46 | * tpl_ => template (the tpl_ will get stripped from the name and element field)
47 | * lng_ => language
48 |
49 | This example registers an extension of the ‘plugin’ type:
50 |
51 | joomla extension:register testsite plg_foobar
52 |
53 | You can use naming without the type prefixes by adding a type argument to the end of the command:
54 |
55 | joomla extension:register testsite foobar package
56 |
57 | In all cases, if the type is not specified or recognized then the default value, component, will be used.
58 |
59 | When registering a plugin type you can use the --folder option to specify the plugin group that will get registered with the record. This defaults to system. Example:
60 |
61 | joomla extension:register testsite foobar plugin --folder=content
62 |
63 | For a language type extension, you should use the --element option to ensure your language files can be loaded correctly:
64 |
65 | joomla extension:register testsite spanglish language --element=en-GB
66 |
67 | When registering a module type extension, you can use the --position option to ensure your module displays where you would like it to. A record gets added to the #_modules table:
68 |
69 | joomla extension:register testsite mod_foobar --position=debug
70 | EOL
71 | )
72 | ->addArgument(
73 | 'type',
74 | InputArgument::OPTIONAL,
75 | 'Type of extension being registered.')
76 | ->addOption(
77 | 'folder',
78 | null,
79 | InputOption::VALUE_REQUIRED,
80 | 'Specifically for the Plugin typed extension, default "system"'
81 | )->addOption(
82 | 'enabled',
83 | null,
84 | InputOption::VALUE_OPTIONAL,
85 | 'Enabled or not, default is "1"',
86 | 1
87 | )->addOption(
88 | 'client_id',
89 | null,
90 | InputOption::VALUE_REQUIRED,
91 | '"0" for Site, "1" for Administrator'
92 | )->addOption(
93 | 'element',
94 | null,
95 | InputOption::VALUE_REQUIRED,
96 | "Provide the element name for languages"
97 | )->addOption(
98 | 'position',
99 | null,
100 | InputOption::VALUE_REQUIRED,
101 | "Provide the position the module should appear"
102 | );
103 | }
104 |
105 | protected function execute(InputInterface $input, OutputInterface $output)
106 | {
107 | parent::execute($input, $output);
108 |
109 | if (Util::isJoomla4($this->target_dir)) {
110 | $output->write("This command is not implemented for Joomla 4\n");
111 |
112 | return 1;
113 | }
114 |
115 | $type = false;
116 |
117 | // passed in type argument
118 | $forceType = $input->getArgument('type');
119 |
120 | // Try to load the type based on naming convention if we aren't passing a 'type' argument
121 | if (!$forceType)
122 | {
123 | $prefix = substr($this->extension, 0, 4);
124 | $type = isset($this->typeMap[$prefix]) ? $this->typeMap[$prefix] : false;
125 | }
126 |
127 | // only allow ones that exist.
128 | if (in_array($forceType, $this->typeMap)) {
129 | $type = $forceType;
130 | }
131 |
132 | // set the type.
133 | if (!$type)
134 | {
135 | $output->writeln("'{$type}' is not allowed as an extension type. Changing to 'component'");
136 | $this->type = 'component';
137 | }
138 | else $this->type = $type;
139 |
140 | $this->check($input, $output);
141 | $this->register($input, $output);
142 |
143 | return 0;
144 | }
145 |
146 | public function register(InputInterface $input, OutputInterface $output)
147 | {
148 | $app = Bootstrapper::getApplication($this->target_dir);
149 |
150 | ob_start();
151 |
152 | // build the record.
153 | $data = new \JObject;
154 | $data->name = $this->extension;
155 | $data->type = $this->type;
156 | $data->element = $this->extension;
157 | $data->client_id = $input->getOption('client_id');
158 | $data->enabled = $input->getOption('enabled');
159 | $data->position = $input->getOption('position');
160 |
161 | $element = $input->getOption('element');
162 | if(strlen($element)){
163 | $data->element = $element;
164 | }
165 |
166 | // special case for plugin, naming and folder.
167 | if($this->type == 'plugin')
168 | {
169 | // set the default folder for plugins only.
170 | $data->folder = $input->getOption('folder') ? $input->getOption('folder') : 'system';
171 |
172 | // special case for the plugins only.
173 | if(substr($data->element, 0, 4) == 'plg_') {
174 | $data->element = substr($data->element, 4);
175 | }
176 | }
177 |
178 | if($this->type == 'template')
179 | {
180 | if(substr($data->name, 0, 4) == 'tpl_') {
181 | $data->name = substr($data->name, 4);
182 | $data->element = substr($data->element, 4);
183 | }
184 | }
185 |
186 | //need to be sure that a prefix is provided for components and modules
187 | if(($this->type == "component" || $this->type == "module") && (strpos($data->element, '_') === false))
188 | {
189 | $prefix = array_search($this->type, $this->typeMap);
190 | $data->element = $prefix . $this->extension;
191 | }
192 |
193 | // get the #__extensions model and table
194 | require_once JPATH_ADMINISTRATOR . '/components/com_installer/models/extension.php';
195 |
196 | $model = new \InstallerModel();
197 | $table = $model->getTable('extension', 'JTable');
198 |
199 | // restrict on same name and type
200 | $unique = array('name' => $data->name, 'type' => $data->type);
201 |
202 | // does the extension exist?
203 | if (!$table->load($unique))
204 | {
205 | if ($table->save($data->getProperties()))
206 | {
207 | if(array_key_exists($this->type, $this->exceptions)){
208 | $this->handleExceptions($output, $app, $data, $this->type);
209 | }
210 |
211 | $output->writeln("Your extension registered as a '{$this->type}', with extension_id: {$table->extension_id}");
212 | } else {
213 | $output->writeln("" . $table->getError() . "");
214 | }
215 | }
216 | else $output->writeln("{$this->extension} {$this->type}: That extension already exists.");
217 |
218 | ob_end_clean();
219 | }
220 |
221 | public function handleExceptions(OutputInterface $output, $app, $data, $extension)
222 | {
223 | $data->title = $this->extension;
224 | $data->published = $data->enabled;
225 |
226 | if($extension == "module"){
227 | $data->module = $this->extension;
228 | }
229 |
230 | if($extension == "template")
231 | {
232 | $data->template = $this->extension;
233 | $data->home = 1;
234 | }
235 |
236 | $exception = $this->exceptions[$this->type];
237 |
238 | foreach($exception['require'] AS $require){
239 | require_once JPATH_ADMINISTRATOR . $require;
240 | }
241 |
242 | $model = new $exception['model'];
243 | $exception_table = $model->getTable($exception['table']['type'], $exception['table']['prefix']);
244 |
245 | if(!$exception_table->save($data->getProperties()))
246 | {
247 | $output->writeln("" . $exception_table->getError() . "");
248 | die();
249 | }
250 | }
251 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Extension/Symlink.php:
--------------------------------------------------------------------------------
1 | array('joomlatools-framework'),
28 | 'pages' => array('joomlatools-framework'),
29 | 'docman' => array('joomlatools-framework'),
30 | 'fileman' => array('joomlatools-framework'),
31 | 'leadman' => array('joomlatools-framework', 'joomlatools-framework-ckeditor'),
32 | 'logman' => array('joomlatools-framework'),
33 | 'textman' => array('joomlatools-framework', 'joomlatools-framework-ckeditor')
34 | );
35 |
36 | protected static $_relative = false;
37 |
38 | public static function registerDependencies($project, array $dependencies)
39 | {
40 | static::$_dependencies[$project] = $dependencies;
41 | }
42 |
43 | public static function registerSymlinker($symlinker)
44 | {
45 | array_unshift(static::$_symlinkers, $symlinker);
46 | }
47 |
48 | protected function configure()
49 | {
50 | parent::configure();
51 |
52 | $this
53 | ->setName('extension:symlink')
54 | ->setDescription('Symlink projects into a site')
55 | ->setHelp(<<--projects-dir directory into the given site. This is ideal for testing your custom extensions while you are working on them.
57 | Your source code should resemble the Joomla directory structure for symlinking to work well. For example, the directory structure of your component should look like this:
58 |
59 | * administrator/components/com_foobar
60 | * components/com_foobar
61 | * media/com_foobar
62 |
63 | To symlink com_foobar into your tesite:
64 |
65 | joomla extension:symlink testsite com_foobar
66 |
67 | You can now use the extension:register or extension:install commands to make your component available to Joomla.
68 |
69 | Note that you can use the site:create command to both create a new site and symlink your projects into it using the --symlink flag.
70 | EOL
71 | )
72 | ->addArgument(
73 | 'symlink',
74 | InputArgument::REQUIRED | InputArgument::IS_ARRAY,
75 | 'A list of directories to symlink from projects directory. Use \'all\' to symlink every directory.'
76 | )
77 | ->addOption(
78 | 'projects-dir',
79 | null,
80 | InputOption::VALUE_REQUIRED,
81 | 'Directory where your custom projects reside',
82 | sprintf('%s/Projects', trim(`echo ~`))
83 | )
84 | ->addOption(
85 | 'relative',
86 | 'r',
87 | InputOption::VALUE_NONE,
88 | 'Use relative paths to the site root instead of absolute paths.'
89 | );
90 | }
91 |
92 | protected function initialize(InputInterface $input, OutputInterface $output)
93 | {
94 | parent::initialize($input, $output);
95 |
96 | $path = dirname(dirname(dirname(__FILE__))).'/Symlinkers';
97 |
98 | if (file_exists($path))
99 | {
100 | foreach (glob($path.'/*.php') as $symlinker) {
101 | require_once $symlinker;
102 | }
103 | }
104 | }
105 |
106 | protected function execute(InputInterface $input, OutputInterface $output)
107 | {
108 | parent::execute($input, $output);
109 |
110 | $this->symlink = $input->getArgument('symlink');
111 |
112 | if (count($this->symlink) == 1 && $this->symlink[0] == 'all')
113 | {
114 | $this->symlink = array();
115 | $source = $input->getOption('projects-dir') . '/*';
116 |
117 | foreach(glob($source, GLOB_ONLYDIR) as $directory) {
118 | $this->symlink[] = basename($directory);
119 | }
120 | }
121 |
122 | $this->projects = array();
123 | foreach ($this->symlink as $symlink)
124 | {
125 | $this->projects[] = $symlink;
126 | $this->projects = array_unique(array_merge($this->projects, $this->_getDependencies($symlink)));
127 | }
128 |
129 | static::$_relative = $input->getOption('relative') === true;
130 |
131 | $this->check($input, $output);
132 | $this->symlinkProjects($input, $output);
133 |
134 | return 0;
135 | }
136 |
137 | public function check(InputInterface $input, OutputInterface $output)
138 | {
139 | if (!file_exists($this->target_dir)) {
140 | throw new \RuntimeException(sprintf('Site not found: %s', $this->site));
141 | }
142 |
143 | $project_dir = $input->getOption('projects-dir');
144 | foreach ($this->projects as $project)
145 | {
146 | $root = $project_dir . '/' . $project;
147 |
148 | if (!is_dir($root)) {
149 | throw new \RuntimeException(sprintf('`%s` could not be found in %s', $project, $project_dir));
150 | }
151 | }
152 | }
153 |
154 | public function symlinkProjects(InputInterface $input, OutputInterface $output)
155 | {
156 | $project_directory = $input->getOption('projects-dir');
157 |
158 | foreach ($this->projects as $project)
159 | {
160 | $result = false;
161 | $root = $project_directory.'/'.$project;
162 |
163 | if (!is_dir($root)) {
164 | continue;
165 | }
166 |
167 | foreach (static::$_symlinkers as $symlinker)
168 | {
169 | $result = call_user_func($symlinker, $root, $this->target_dir, $project, $this->projects, $output);
170 |
171 | if ($result === true) {
172 | break;
173 | }
174 | }
175 |
176 | if (!$result) {
177 | $this->_symlink($root, $this->target_dir, $output);
178 | }
179 | }
180 | }
181 |
182 | /**
183 | * Default symlinker
184 | *
185 | * @param $project
186 | * @param $destination
187 | * @param $name
188 | * @param $projects
189 | * @return bool
190 | */
191 | protected function _symlink($project, $destination, OutputInterface $output)
192 | {
193 | if (is_dir($project.'/code')) {
194 | $project .= '/code';
195 | }
196 |
197 | $iterator = new Iterator($project, $destination);
198 | $iterator->setOutput($output);
199 |
200 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
201 | $output->writeln("Symlinking `$project` into `$destination`");
202 | }
203 |
204 | while ($iterator->valid()) {
205 | $iterator->next();
206 | }
207 |
208 | return true;
209 | }
210 |
211 | /**
212 | * Look for the dependencies of the dependency
213 | *
214 | * @param string $project The directory name of Project
215 | * @return array An array of dependencies
216 | */
217 | protected function _getDependencies($project)
218 | {
219 | $projects = array();
220 | $dependencies = static::$_dependencies;
221 |
222 | if(array_key_exists($project, $dependencies) && is_array($dependencies[$project]))
223 | {
224 | $projects = $dependencies[$project];
225 |
226 | foreach ($projects as $dependency) {
227 | $projects = array_merge($projects, $this->_getDependencies($dependency));
228 | }
229 | }
230 |
231 | return $projects;
232 | }
233 |
234 | public static function buildSymlinkPath($source, $target)
235 | {
236 | $source = realpath($source);
237 |
238 | if (static::$_relative === true)
239 | {
240 | $separator = DIRECTORY_SEPARATOR;
241 | $from = is_dir($target) ? $target : dirname($target);
242 |
243 | // In some cases a path that has been concatenated from
244 | // different strings contains double forward slashes.
245 | // Make sure to replace these so we don't get incorrect paths:
246 | $from = str_replace($separator.$separator, $separator, $from);
247 | $source = str_replace($separator.$separator, $separator, $source);
248 |
249 | $partsFrom = explode($separator, rtrim($from, $separator));
250 | $partsTo = explode($separator, rtrim($source, $separator));
251 |
252 | while(count($partsFrom) && count($partsTo) && ($partsFrom[0] == $partsTo[0]))
253 | {
254 | array_shift($partsFrom);
255 | array_shift($partsTo);
256 | }
257 |
258 | $prefix = str_repeat(sprintf('..%s', $separator), count($partsFrom));
259 | $suffix = implode($separator, $partsTo);
260 |
261 | $source = $prefix . $suffix;
262 | }
263 |
264 | return $source;
265 | }
266 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Plugin/Install.php:
--------------------------------------------------------------------------------
1 | setName('plugin:install')
22 | ->setDescription('Install plugin')
23 | ->setHelp(<<joomlatools/console-backup:
26 |
27 | joomla plugin:install joomlatools/console-backup
28 |
29 | You can specify a specific version or branch by appending the version number to the package name. Version constraints follow Composer’s convention:
30 |
31 | joomla plugin:install joomlatools/console-backup:dev-develop
32 |
33 | Refer to the online documentation at the following URL on how to write your own plugins: http://developer.joomlatools.com/tools/console/plugins.html#creating-custom-plugins
34 | EOF
35 | )
36 | ->addArgument(
37 | 'package',
38 | InputArgument::REQUIRED,
39 | 'The Composer package name and version. Example: vendor/foo-bar:1.*'
40 | );
41 | }
42 |
43 | protected function execute(InputInterface $input, OutputInterface $output)
44 | {
45 | if (Util::isJoomla4($this->target_dir)) {
46 | $output->write("This command is not implemented for Joomla 4\n");
47 |
48 | return;
49 | }
50 |
51 | $result = shell_exec('command -v composer >/dev/null 2>&1 || { echo "false"; }');
52 |
53 | if (trim($result) == 'false')
54 | {
55 | $output->writeln('Composer was not found. It is either not installed or globally available');
56 | return 99;
57 | }
58 |
59 | $plugin_path = $this->getApplication()->getPluginPath();
60 |
61 | if (!file_exists($plugin_path)) {
62 | `mkdir -p $plugin_path`;
63 | }
64 |
65 | $package = $input->getArgument('package');
66 |
67 | if (strpos($package, ':') === false)
68 | {
69 | $name = $package;
70 | $version = '';
71 | }
72 | else list($name, $version) = explode(':', $package);
73 |
74 | exec("composer show --no-interaction --all $name $version 2>&1", $result, $code);
75 |
76 | if ($code === 1)
77 | {
78 | $output->writeln("The $package plugin you are attempting to install cannot be found");
79 | return 99;
80 | }
81 |
82 | $type = '';
83 |
84 | foreach ($result as $line => $content)
85 | {
86 | $content = trim($content);
87 |
88 | if (strpos($content, 'type') === 0) {
89 | $parts = explode(':', $content);
90 |
91 | if (count($parts) > 1)
92 | {
93 | $type = trim($parts[1]);
94 | break;
95 | }
96 | }
97 | }
98 |
99 | if ($type != 'joomlatools-console-plugin')
100 | {
101 | $output->writeln("$package is not a Joomla console plugin");
102 | $output->writeln('Plugin not installed');
103 | return 99;
104 | }
105 |
106 | passthru("composer --no-interaction --no-progress --working-dir=$plugin_path require $package");
107 |
108 | return 0;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Plugin/ListAll.php:
--------------------------------------------------------------------------------
1 | setName('plugin:list')
22 | ->setDescription('List installed plugins');
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output)
26 | {
27 | if (Util::isJoomla4($this->target_dir)) {
28 | $output->write("This command is not implemented for Joomla 4\n");
29 |
30 | return;
31 | }
32 |
33 | $plugins = $this->getApplication()->getPlugins();
34 |
35 | $packages = array_keys($plugins);
36 | $versions = array_values($plugins);
37 |
38 | $combine = function($a, $b) {
39 | return array($a, $b);
40 | };
41 |
42 | $rows = array_map($combine, $packages, $versions);
43 |
44 | $headers = array('Plugin package', 'Version');
45 |
46 | $table = new Table($output);
47 |
48 | $table->setHeaders($headers)
49 | ->setRows($rows)
50 | ->render($output);
51 |
52 | return 0;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Plugin/Uninstall.php:
--------------------------------------------------------------------------------
1 | setName('plugin:uninstall')
22 | ->setDescription('Used for uninstalling plugins')
23 | ->addArgument(
24 | 'package',
25 | InputArgument::REQUIRED,
26 | 'The composer package containing the plugin to uninstall'
27 | );
28 | }
29 |
30 | protected function execute(InputInterface $input, OutputInterface $output)
31 | {
32 | if (Util::isJoomla4($this->target_dir)) {
33 | $output->write("This command is not implemented for Joomla 4\n");
34 |
35 | return;
36 | }
37 |
38 | $plugins = $this->getApplication()->getPlugins();
39 | $path = $this->getApplication()->getPluginPath();
40 |
41 | $package = $input->getArgument('package');
42 |
43 | $result = `command -v composer >/dev/null 2>&1 || { echo >&2 "false"; }`;
44 |
45 | if ($result == 'false')
46 | {
47 | $output->writeln('Composer was not found. It is either not installed or globally available.');
48 | return 99;
49 | }
50 |
51 | if (!array_key_exists($package, $plugins))
52 | {
53 | $output->writeln('Error:The package "' . $package . '" is not installed');
54 | return 99;
55 | }
56 |
57 | passthru("composer --no-interaction --working-dir=$path remove $package");
58 |
59 | return 0;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/AbstractSite.php:
--------------------------------------------------------------------------------
1 | addArgument(
36 | 'site',
37 | InputArgument::REQUIRED,
38 | 'Alphanumeric site name. Also used in the site URL with .test domain'
39 | )->addOption(
40 | 'www',
41 | null,
42 | InputOption::VALUE_REQUIRED,
43 | "Web server root",
44 | '/var/www'
45 | )
46 | ->addOption(
47 | 'use-webroot-dir',
48 | null,
49 | InputOption::VALUE_NONE,
50 | "Uses directory specified with --www as the site install dir"
51 | )
52 | ;
53 | }
54 |
55 | protected function execute(InputInterface $input, OutputInterface $output)
56 | {
57 | $this->site = $input->getArgument('site');
58 | $this->www = $input->getOption('www');
59 |
60 | if ($input->getOption('use-webroot-dir')) {
61 | $this->target_dir = $this->www;
62 | } else {
63 | $this->target_dir = $this->www.'/'.$this->site;
64 | }
65 |
66 | return 0;
67 | }
68 |
69 | /**
70 | * Prompt user to fill in a value
71 | *
72 | * @param InputInterface $input
73 | * @param OutputInterface $output
74 | * @param $label string The description of the value
75 | * @param $default string|array The default value. If array given, question will be multiple-choice and the first item will be default. Can also be empty.
76 | * @param bool $required
77 | * @param bool $hidden Hide user input (useful for passwords)
78 | *
79 | * @return string Answer
80 | */
81 | protected function _ask(InputInterface $input, OutputInterface $output, $label, $default = '', $required = false, $hidden = false)
82 | {
83 | $helper = $this->getHelper('question');
84 | $text = $label;
85 |
86 | if (is_array($default)) {
87 | $defaultValue = $default[0];
88 | }
89 | else $defaultValue = $default;
90 |
91 | if (!empty($defaultValue)) {
92 | $text .= ' [default: ' . $defaultValue . ']';
93 | }
94 |
95 | $text .= ': ';
96 |
97 | if (is_array($default)) {
98 | $question = new Question\ChoiceQuestion($text, $default, 0);
99 | }
100 | else $question = new Question\Question($text, $default);
101 |
102 | if ($hidden === true) {
103 | $question->setHidden(true);
104 | }
105 |
106 | $answer = $helper->ask($input, $output, $question);
107 |
108 | if ($required && empty($answer)) {
109 | return $this->_ask($input, $output, $label, $default, $hidden);
110 | }
111 |
112 | return $answer;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/Configure.php:
--------------------------------------------------------------------------------
1 | setName('site:configure')
47 | ->setDescription('Configure a Joomla site')
48 | ->addOption(
49 | 'overwrite',
50 | null,
51 | InputOption::VALUE_NONE,
52 | 'Overwrite configuration.php or .env file if it already exists'
53 | )
54 | ->addOption(
55 | 'interactive',
56 | null,
57 | InputOption::VALUE_NONE,
58 | 'Prompt for configuration details'
59 | )
60 | ->addOption(
61 | 'options',
62 | null,
63 | InputOption::VALUE_REQUIRED,
64 | "A YAML file consisting of serialized parameters to override JConfig"
65 | )
66 | ;
67 | }
68 |
69 | protected function execute(InputInterface $input, OutputInterface $output)
70 | {
71 | parent::execute($input, $output);
72 |
73 | $random = function($length) {
74 | $charset ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
75 | $string = '';
76 | $count = strlen($charset);
77 |
78 | while ($length--) {
79 | $string .= $charset[mt_rand(0, $count-1)];
80 | }
81 |
82 | return $string;
83 | };
84 |
85 | $this->_default_values = array(
86 | 'log_path' => $this->target_dir . '/logs/',
87 | 'tmp_path' => $this->target_dir . '/tmp/',
88 | 'sitename' => $this->site,
89 | 'key' => $random(16),
90 | 'env' => 'development'
91 | );
92 |
93 | if ($input->getOption('interactive')) {
94 | $this->_promptDetails($input, $output);
95 | }
96 |
97 | $options = $input->getOption('options');
98 | if ($options !== null)
99 | {
100 | if (!file_exists($options)) {
101 | throw new \Exception(sprintf('Additional option file \'%s\' does not exist', $options));
102 | }
103 |
104 | $contents = file_get_contents($options);
105 |
106 | try {
107 | $this->_extra_options = Yaml::parse($contents);
108 | }
109 | catch (\Exception $ex) {
110 | throw new \Exception(sprintf('Unable to parse YAML file %s', $options));
111 | }
112 | }
113 |
114 | $this->check($input, $output);
115 | $this->_configureJoomlaCMS();
116 |
117 | return 0;
118 | }
119 |
120 | protected function _configureJoomlaCMS()
121 | {
122 | $source = $this->target_dir.'/_installation/configuration.php-dist';
123 | if (!file_exists($source)) {
124 | $source = $this->target_dir.'/installation/configuration.php-dist';
125 | }
126 |
127 | $target = $this->target_dir.'/configuration.php';
128 |
129 | $contents = file_get_contents($source);
130 | $replace = function($name, $value, &$contents) {
131 | $pattern = sprintf('#\$%s\s*=\s*(["\']?).*?\1(?=[;\1])#', $name);
132 | $match = preg_match($pattern, $contents);
133 | $value = is_numeric($value) ? $value : "'" . str_replace("'", "\\'", $value) . "'";
134 |
135 | if(!$match)
136 | {
137 | $pattern = "/^\s*}\s*$/m";
138 | $replacement = sprintf("\tpublic \$%s = %s;\n}", $name, $value);
139 | }
140 | else $replacement = sprintf("\$%s = %s", $name, $value);
141 |
142 | $contents = preg_replace($pattern, $replacement, $contents);
143 | };
144 | $remove = function($name, &$contents) {
145 | $pattern = sprintf('#public\s+\$%s\s*=\s*(["\']?).*?\1(?=[;\1])\s*;#', $name);
146 | $contents = preg_replace($pattern, '', $contents);
147 | };
148 |
149 | $replacements = array(
150 | 'db' => $this->target_db,
151 | 'user' => $this->mysql->user,
152 | 'password' => $this->mysql->password,
153 | 'host' => $this->mysql->host,
154 | 'dbprefix' => 'j_',
155 | 'dbtype' => $this->mysql->driver,
156 |
157 | 'mailer' => 'smtp',
158 | 'mailfrom' => 'admin@example.com',
159 | 'fromname' => $this->site,
160 | 'smtpauth' => '0',
161 | 'smtpuser' => '',
162 | 'smtppass' => '',
163 | 'smtphost' => 'localhost',
164 | 'smtpsecure' => 'none',
165 | 'smtpport' => '1025',
166 |
167 | 'sef' => '1',
168 | 'sef_rewrite' => '1',
169 | 'unicodeslugs' => '1',
170 |
171 | 'debug' => '1',
172 | 'lifetime' => '600',
173 | 'tmp_path' => $this->_default_values['tmp_path'],
174 | 'log_path' => $this->_default_values['log_path'],
175 | 'sitename' => $this->_default_values['sitename'],
176 |
177 | 'secret' => $this->_default_values['key']
178 | );
179 |
180 | if ($this->mysql->port != $this->getDefaultPort()) {
181 | $replacements['host'] .= ':' . $this->mysql->port;
182 | }
183 |
184 | $configuration = array_merge($replacements, $this->_extra_options);
185 | foreach($configuration as $key => $value) {
186 | $replace($key, $value, $contents);
187 | }
188 |
189 | $remove('root_user', $contents);
190 |
191 | file_put_contents($target, $contents);
192 | chmod($target, 0664);
193 |
194 | if (file_exists($this->target_dir.'/installation')) {
195 | `mv $this->target_dir/installation $this->target_dir/_installation`;
196 | }
197 | }
198 |
199 | /**
200 | * Get default port for MySQL
201 | *
202 | * @return string
203 | */
204 | protected function getDefaultPort()
205 | {
206 | $driver = $this->mysql->driver;
207 | $key = $driver . '.default_port';
208 | $port = ini_get($key);
209 |
210 | if ($port) {
211 | return $port;
212 | }
213 |
214 | return ini_get('mysqli.default_port');
215 | }
216 |
217 | public function check(InputInterface $input, OutputInterface $output)
218 | {
219 | if (!file_exists($this->target_dir)) {
220 | throw new \RuntimeException(sprintf('Site %s not found', $this->site));
221 | }
222 |
223 | if (!$input->getOption('overwrite'))
224 | {
225 | $file = 'configuration.php';
226 |
227 | if (file_exists($this->target_dir . '/' . $file)) {
228 | throw new \RuntimeException(sprintf('Site %s is already configured', $this->site));
229 | }
230 | }
231 | }
232 |
233 | /**
234 | * Tell the object to skip the database prompts
235 | * in interactive mode or not.
236 | *
237 | * @param $value bool
238 | */
239 | public function skipDatabasePrompt($value = true)
240 | {
241 | $this->_skip_database_prompt = $value;
242 | }
243 |
244 | protected function _promptDetails(InputInterface $input, OutputInterface $output)
245 | {
246 | if (!$this->_skip_database_prompt) {
247 | $this->_promptDatabaseDetails($input, $output);
248 | }
249 |
250 | $this->_default_values['sitename'] = $this->_ask($input, $output, 'Site Name', $this->_default_values['sitename'], true);
251 |
252 | $this->_default_values['tmp_path'] = $this->_ask($input, $output, 'Temporary path', $this->_default_values['tmp_path'], true);
253 | $this->_default_values['log_path'] = $this->_ask($input, $output, 'Log path', $this->_default_values['log_path'], true);
254 | $this->_default_values['key'] = $this->_ask($input, $output, 'Secret Key', $this->_default_values['key'], true);
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/Create.php:
--------------------------------------------------------------------------------
1 | setName('site:create')
40 | ->setDescription('Create a new Joomla site from scratch')
41 | ->setHelp(<<joomla site:create foobar
45 |
46 | The newly installed site will be available at /var/www/foobar and foobar.test after that. You can login into your fresh Joomla installation using these credentials: admin/admin.
47 | By default, the web server root is set to /var/www. You can pass –www=/my/server/path to commands for custom values.
48 |
49 | You can choose the Joomla version or the sample data to be installed. A more elaborate example:
50 |
51 | joomla site:create testsite --release=2.5 --sample-data=blog
52 | EOF
53 | )
54 | ->addOption(
55 | 'release',
56 | null,
57 | InputOption::VALUE_REQUIRED,
58 | "Joomla version. Can be a release number (2, 3.2, ..) or branch name. Run `joomla versions` for a full list.\nUse \"none\" for an empty virtual host.",
59 | 'latest'
60 | )
61 | ->addOption(
62 | 'sample-data',
63 | null,
64 | InputOption::VALUE_REQUIRED,
65 | 'Sample data to install (default|blog|brochure|learn|testing)'
66 | )
67 | ->addOption(
68 | 'symlink',
69 | null,
70 | InputOption::VALUE_REQUIRED,
71 | 'A comma separated list of directories to symlink from the projects directory. Use \'all\' to symlink every folder.'
72 | )
73 | ->addOption(
74 | 'repo',
75 | null,
76 | InputOption::VALUE_REQUIRED,
77 | 'Alternative Git repository to use. Also accepts a gzipped tar archive instead of a Git repository.'
78 | )
79 | ->addOption(
80 | 'clear-cache',
81 | null,
82 | InputOption::VALUE_NONE,
83 | 'Update the list of available tags and branches from the Joomla repository'
84 | )
85 | ->addOption(
86 | 'projects-dir',
87 | null,
88 | InputOption::VALUE_REQUIRED,
89 | 'Directory where your custom projects reside',
90 | sprintf('%s/Projects', trim(`echo ~`))
91 | )
92 | ->addOption(
93 | 'http-port',
94 | null,
95 | InputOption::VALUE_REQUIRED,
96 | 'The HTTP port the virtual host should listen to',
97 | 80
98 | )
99 | ->addOption(
100 | 'ssl-port',
101 | null,
102 | InputOption::VALUE_REQUIRED,
103 | 'The port on which the server will listen for SSL requests',
104 | 443
105 | )
106 | ->addOption(
107 | 'interactive',
108 | null,
109 | InputOption::VALUE_NONE,
110 | 'Prompt for configuration details'
111 | )
112 | ->addOption(
113 | 'options',
114 | null,
115 | InputOption::VALUE_REQUIRED,
116 | "A YAML file consisting of serialized parameters to override JConfig."
117 | )
118 | ->addOption(
119 | 'clone',
120 | null,
121 | InputOption::VALUE_OPTIONAL,
122 | 'Clone the Git repository instead of creating a copy in the target directory. Use --clone=shallow for a shallow clone or leave empty.',
123 | true
124 | )
125 | ->addOption(
126 | 'skip-create-statement',
127 | null,
128 | InputOption::VALUE_NONE,
129 | 'Do not run the "CREATE IF NOT EXISTS " query. Use this if the user does not have CREATE privileges on the database.'
130 | )
131 | ->addOption(
132 | 'vhost',
133 | null,
134 | InputOption::VALUE_NEGATABLE,
135 | 'Create an Apache vhost for the site',
136 | true
137 | )
138 | ->addOption(
139 | 'vhost-template',
140 | null,
141 | InputOption::VALUE_REQUIRED,
142 | 'Custom file to use as the Apache vhost configuration. Make sure to include HTTP and SSL directives if you need both.',
143 | null
144 | )
145 | ->addOption(
146 | 'vhost-folder',
147 | null,
148 | InputOption::VALUE_REQUIRED,
149 | 'The Apache2 vhost folder',
150 | null
151 | )
152 | ->addOption(
153 | 'vhost-filename',
154 | null,
155 | InputOption::VALUE_OPTIONAL,
156 | 'The Apache2 vhost file name',
157 | null,
158 | )
159 | ->addOption(
160 | 'vhost-restart-command',
161 | null,
162 | InputOption::VALUE_OPTIONAL,
163 | 'The full command for restarting Apache2',
164 | null
165 | )
166 | ->addOption(
167 | 'chown',
168 | null,
169 | InputOption::VALUE_OPTIONAL,
170 | 'Change file owner as the passed user'
171 | )
172 | ;
173 | }
174 |
175 | protected function execute(InputInterface $input, OutputInterface $output)
176 | {
177 | parent::execute($input, $output);
178 |
179 | $this->version = $input->getOption('release');
180 |
181 | $this->check($input, $output);
182 |
183 | $this->download($input, $output);
184 |
185 | if ($input->getOption('vhost')) {
186 | $this->addVirtualHost($input, $output);
187 | }
188 |
189 | if (!file_exists($this->target_dir)) {
190 | `mkdir -p $this->target_dir`;
191 | }
192 |
193 | if ($this->version != 'none')
194 | {
195 | $arguments = array(
196 | 'site:install',
197 | 'site' => $this->site,
198 | '--www' => $this->www
199 | );
200 |
201 | $optionalArgs = array('sample-data', 'symlink', 'projects-dir', 'interactive', 'mysql-login', 'mysql-db-prefix', 'mysql-host', 'mysql-port', 'mysql-database', 'options', 'skip-create-statement', 'use-webroot-dir');
202 | foreach ($optionalArgs as $optionalArg)
203 | {
204 | $value = $input->getOption($optionalArg);
205 | if (!empty($value)) {
206 | $arguments['--' . $optionalArg] = $value;
207 | }
208 | }
209 |
210 | $command = new Install();
211 | $command->setApplication($this->getApplication());
212 | $command->run(new ArrayInput($arguments), $output);
213 | }
214 |
215 | if ($input->hasOption('chown')) {
216 | $user = $input->getOption('chown');
217 | `chown -R $user:$user $this->target_dir`;
218 | }
219 |
220 | /*
221 | * Run all site:create:* commands after site creation
222 | */
223 | try {
224 | $commands = $this->getApplication()->all('site:create');
225 |
226 | foreach ($commands as $command) {
227 | $arguments = array(
228 | $command->getName(),
229 | 'site' => $this->site,
230 | '--www' => $this->www
231 | );
232 | $command->setApplication($this->getApplication());
233 | $command->run(new ArrayInput($arguments), $output);
234 | }
235 | }
236 | catch (\Symfony\Component\Console\Exception\NamespaceNotFoundException $e) {}
237 | catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) {}
238 |
239 |
240 | return 0;
241 | }
242 |
243 | public function check(InputInterface $input, OutputInterface $output)
244 | {
245 | if (file_exists($this->target_dir) && !is_dir($this->target_dir)) {
246 | throw new \RuntimeException(sprintf('A file named \'%s\' already exists', $this->site));
247 | }
248 |
249 | if (is_dir($this->target_dir) && count(scandir($this->target_dir)) > 2) {
250 | throw new \RuntimeException(sprintf('Site directory \'%s\' is not empty.', $this->site));
251 | }
252 | }
253 |
254 | public function download(InputInterface $input, OutputInterface $output)
255 | {
256 | $arguments = array(
257 | 'site:download',
258 | 'site' => $this->site,
259 | '--release' => $input->getOption('release'),
260 | '--clear-cache' => $input->getOption('clear-cache'),
261 | '--www' => $this->www,
262 | '--use-webroot-dir' => $input->getOption('use-webroot-dir')
263 | );
264 |
265 | if ($input->hasParameterOption('--clone')) {
266 | $arguments['--clone'] = $input->getOption('clone');
267 | }
268 |
269 | $repo = $input->getOption('repo');
270 | if (!empty($repo)) {
271 | $arguments['--repo'] = $repo;
272 | }
273 |
274 | $command = new Download();
275 | $command->run(new ArrayInput($arguments), $output);
276 | }
277 |
278 | public function addVirtualHost(InputInterface $input, OutputInterface $output)
279 | {
280 | $command_input = array(
281 | 'vhost:create',
282 | 'site' => $this->site,
283 | '--http-port' => $input->getOption('http-port'),
284 | '--ssl-port' => $input->getOption('ssl-port'),
285 | '--www' => $input->getOption('www'),
286 | '--use-webroot-dir' => $input->getOption('use-webroot-dir'),
287 | );
288 |
289 | foreach (array('template', 'folder', 'filename', 'restart-command') as $vhostkey) {
290 | if ($input->getOption('vhost-'.$vhostkey) !== null) {
291 | $command_input['--'.$vhostkey] = $input->getOption('vhost-'.$vhostkey);
292 | }
293 | }
294 |
295 | $command = new Vhost\Create();
296 | $command->run(new ArrayInput($command_input), $output);
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/Delete.php:
--------------------------------------------------------------------------------
1 | setName('site:delete')
26 | ->setDescription('Delete a site')
27 | ->addOption(
28 | 'skip-database',
29 | null,
30 | InputOption::VALUE_NONE,
31 | 'Leave the database intact'
32 | )
33 | ->addOption(
34 | 'skip-vhost',
35 | null,
36 | InputOption::VALUE_NONE,
37 | 'Leave the virtual host intact'
38 | )
39 | ;
40 | }
41 |
42 | protected function execute(InputInterface $input, OutputInterface $output)
43 | {
44 | parent::execute($input, $output);
45 |
46 | $this->check($input, $output);
47 | $this->deleteDirectory($input, $output);
48 | $this->deleteVirtualHost($input, $output);
49 | $this->deleteDatabase($input, $output);
50 |
51 | return 0;
52 | }
53 |
54 | public function check(InputInterface $input, OutputInterface $output)
55 | {
56 | if (getcwd() === $this->target_dir && getcwd() !== $this->www) {
57 | throw new \RuntimeException('You are currently in the directory you are trying to delete. Aborting');
58 | }
59 | }
60 |
61 | public function deleteDirectory(InputInterface $input, OutputInterface $output)
62 | {
63 | `rm -rf $this->target_dir`;
64 | }
65 |
66 | public function deleteDatabase(InputInterface $input, OutputInterface $output)
67 | {
68 | if ($input->getOption('skip-database')) {
69 | return;
70 | }
71 |
72 | $arguments = array(
73 | 'database:drop',
74 | 'site' => $this->site
75 | );
76 |
77 | $optionalArgs = array('mysql-login', 'mysql-db-prefix', 'mysql-host', 'mysql-port', 'mysql-database');
78 | foreach ($optionalArgs as $optionalArg)
79 | {
80 | $value = $input->getOption($optionalArg);
81 | if (!empty($value)) {
82 | $arguments['--' . $optionalArg] = $value;
83 | }
84 | }
85 |
86 | $command = new Database\Drop();
87 | $command->run(new ArrayInput($arguments), $output);
88 | }
89 |
90 | public function deleteVirtualHost(InputInterface $input, OutputInterface $output)
91 | {
92 | if ($input->getOption('skip-vhost')) {
93 | return;
94 | }
95 |
96 | $command_input = new ArrayInput(array(
97 | 'vhost:remove',
98 | 'site' => $this->site
99 | ));
100 |
101 | $command = new Vhost\Remove();
102 | $command->run($command_input, $output);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/Download.php:
--------------------------------------------------------------------------------
1 | setName('site:download')
49 | ->setDescription('Download and extract the given Joomla version')
50 | ->addOption(
51 | 'release',
52 | null,
53 | InputOption::VALUE_REQUIRED,
54 | "Joomla version. Can be a release number (2, 3.2, ..) or branch name. Run `joomla versions` for a full list.\nUse \"none\" for an empty virtual host.",
55 | 'latest'
56 | )
57 | ->addOption(
58 | 'refresh',
59 | null,
60 | InputOption::VALUE_NONE,
61 | 'Update the list of available tags and branches from the Joomla repository'
62 | )
63 | ->addOption(
64 | 'clear-cache',
65 | null,
66 | InputOption::VALUE_NONE,
67 | 'Clear the downloaded files cache'
68 | )
69 | ->addOption(
70 | 'repo',
71 | null,
72 | InputOption::VALUE_REQUIRED,
73 | 'Alternative Git repository to clone. Also accepts a gzipped tar archive instead of a Git repository.'
74 | )
75 | ->addOption(
76 | 'clone',
77 | null,
78 | InputOption::VALUE_OPTIONAL,
79 | 'Clone the Git repository instead of creating a copy in the target directory. Use --clone=shallow for a shallow clone or leave empty.',
80 | true
81 | )
82 | ->addOption(
83 | 'chown',
84 | null,
85 | InputOption::VALUE_OPTIONAL,
86 | 'Change file owner as the passed user'
87 | )
88 | ;
89 | }
90 |
91 | protected function execute(InputInterface $input, OutputInterface $output)
92 | {
93 | $this->output = $output;
94 | $this->input = $input;
95 |
96 | parent::execute($input, $output);
97 |
98 | $this->check($input, $output);
99 |
100 | $this->versions = new Versions();
101 |
102 | $repo = $input->getOption('repo');
103 |
104 | if (empty($repo)) {
105 | $repo = Versions::REPO_JOOMLA_CMS;
106 | }
107 |
108 | $this->versions->setRepository($repo);
109 |
110 | if ($input->getOption('refresh')) {
111 | $this->versions->refresh();
112 | }
113 |
114 | if ($input->getOption('clear-cache')) {
115 | $this->versions->clearcache($output);
116 | }
117 |
118 | $this->setVersion($input->getOption('release'));
119 |
120 | if (strtolower($this->version) == 'none') {
121 | return 0;
122 | }
123 |
124 | if ($input->hasParameterOption('--clone')) {
125 | $this->_setupClone();
126 | }
127 | else $this->_setupCopy();
128 |
129 |
130 | $directory = $this->target_dir;
131 | if (file_exists($directory.'/htaccess.txt')) {
132 | `cp $directory/htaccess.txt $directory/.htaccess`;
133 | }
134 |
135 | return 0;
136 | }
137 |
138 | public function check(InputInterface $input, OutputInterface $output)
139 | {
140 | if (count(glob("$this->target_dir/*", GLOB_NOSORT)) !== 0) {
141 | throw new \RuntimeException(sprintf('Target directory %s is not empty', $this->target_dir));
142 | }
143 | }
144 |
145 | public function setVersion($version)
146 | {
147 | if (!$this->versions->isGitRepository())
148 | {
149 | $this->version = 'current';
150 | return;
151 | }
152 |
153 | if ($version == 'none')
154 | {
155 | $this->version = $version;
156 | return;
157 | }
158 |
159 | $result = strtolower($version);
160 |
161 | if (strtolower($version) === 'latest') {
162 | $result = $this->versions->getLatestRelease();
163 | }
164 | else
165 | {
166 | $length = strlen($version);
167 | $format = is_numeric($version) || preg_match('/^v?\d(\.\d+)?$/im', $version);
168 |
169 | if (substr($version, 0, 1) == 'v') {
170 | $length--;
171 | }
172 |
173 | if ( ($length == 1 || $length == 3) && $format)
174 | {
175 | $result = $this->versions->getLatestRelease($version);
176 |
177 | if($result == '0.0.0') {
178 | $result = $version.($length == 1 ? '.0.0' : '.0');
179 | }
180 | }
181 | }
182 |
183 | if (!$this->versions->isBranch($result))
184 | {
185 | $isTag = $this->versions->isTag($result);
186 |
187 | if (!$isTag)
188 | {
189 | $original = $result;
190 | if (substr($original, 0, 1) == 'v') {
191 | $result = substr($original, 1);
192 | }
193 | else $result = 'v' . $original;
194 |
195 | if (!$this->versions->isTag($result)) {
196 | throw new \RuntimeException(sprintf('Failed to find tag or branch "%s". Please refresh the version list first: `joomla versions --refresh`', $original));
197 | }
198 | }
199 | }
200 |
201 | $this->version = $result;
202 | }
203 |
204 | protected function _setupCopy()
205 | {
206 | $tarball = $this->_getTarball();
207 |
208 | if (!$this->_isValidTarball($tarball))
209 | {
210 | if (file_exists($tarball)) {
211 | unlink($tarball);
212 | }
213 |
214 | throw new \RuntimeException(sprintf('Downloaded tar archive "%s" could not be verified. A common cause is an interrupted download: check your internet connection and try again.', basename($tarball)));
215 | }
216 |
217 | if (!file_exists($this->target_dir)) {
218 | `mkdir -p $this->target_dir`;
219 | }
220 |
221 | if (!$this->versions->isBranch($this->version) && \version_compare($this->version, '4.0.0', '>=')) {
222 | `cd $this->target_dir; tar xzf $tarball`;
223 | } else {
224 | `cd $this->target_dir; tar xzf $tarball --strip 1`;
225 | }
226 |
227 | if ($this->versions->isBranch($this->version)) {
228 | unlink($tarball);
229 | }
230 | }
231 |
232 | protected function _setupClone()
233 | {
234 | if (!$this->versions->isGitRepository()) {
235 | throw new \RuntimeException(sprintf('The --clone flag requires a valid Git repository'));
236 | }
237 |
238 | $this->_clone($this->target_dir, $this->version);
239 | }
240 |
241 | protected function _getTarball()
242 | {
243 | $tar = $this->version.'.tar.gz';
244 | // Replace forward slashes with a dash, otherwise the path looks like it contains more subdirectories
245 | $tar = str_replace('/', '-', $tar);
246 |
247 | $cache = $this->versions->getCacheDirectory().'/'.$tar;
248 |
249 | $repository = $this->versions->getRepository();
250 |
251 | if ($this->versions->isGitRepository())
252 | {
253 |
254 | if (file_exists($cache) && !$this->versions->isBranch($this->version)) {
255 | return $cache;
256 | }
257 |
258 | if ($repository === 'https://github.com/joomla/joomla-cms' && !$this->versions->isBranch($this->version)
259 | && \version_compare($this->version, '4.0.0', '>=')) {
260 | $result = $this->_downloadJoomlaRelease($cache);
261 | } else {
262 | $scheme = strtolower(parse_url($repository, PHP_URL_SCHEME));
263 | $isGitHub = strtolower(parse_url($repository, PHP_URL_HOST)) == 'github.com';
264 | $extension = substr($repository, -4);
265 |
266 | if (in_array($scheme, array('http', 'https')) && $isGitHub && $extension != '.git') {
267 | $result = $this->_downloadFromGitHub($cache);
268 | }
269 | else
270 | {
271 | $directory = $this->versions->getCacheDirectory() . '/source';
272 |
273 | if ($this->_clone($directory)) {
274 | $result = $this->_archive($directory, $cache);
275 | }
276 | }
277 | }
278 | }
279 | else $result = $this->_download($cache);
280 |
281 | if (!$result) {
282 | throw new \RuntimeException(sprintf('Failed to download source files for Joomla %s', $this->version));
283 | }
284 |
285 | return $cache;
286 | }
287 |
288 | protected function _downloadJoomlaRelease($target)
289 | {
290 | $url = $this->versions->getRepository();
291 |
292 | $url .= "/releases/download/{$this->version}/Joomla_{$this->version}-Stable-Full_Package.tar.gz";
293 |
294 | $this->output->writeln("Downloading $url - this could take a few minutes ..");
295 |
296 | $opts = array(
297 | 'http' => array('method' => 'GET',
298 | 'max_redirects' => '20')
299 | );
300 |
301 | $context = stream_context_create($opts);
302 | $bytes = file_put_contents($target, fopen($url, 'r', false, $context));
303 |
304 | return (bool) $bytes;
305 | }
306 |
307 | /**
308 | * Downloads codebase from GitHub via HTTP
309 | *
310 | * @param $target
311 | * @return bool
312 | */
313 | protected function _downloadFromGitHub($target)
314 | {
315 | $url = $this->versions->getRepository();
316 |
317 | if ($this->versions->isBranch($this->version)) {
318 | $url .= '/tarball/' . $this->version;
319 | }
320 | else $url .= '/archive/'.$this->version.'.tar.gz';
321 |
322 | $this->output->writeln("Downloading $url - this could take a few minutes ..");
323 |
324 | $bytes = file_put_contents($target, fopen($url, 'r'));
325 |
326 | return (bool) $bytes;
327 | }
328 |
329 | /**
330 | * Downloads codebase via HTTP
331 | *
332 | * @param $target
333 | * @return bool
334 | */
335 | protected function _download($target)
336 | {
337 | $url = $this->versions->getRepository();
338 |
339 | $this->output->writeln("Downloading $url - this could take a few minutes ..");
340 |
341 | $bytes = file_put_contents($target, fopen($url, 'r'));
342 |
343 | return (bool) $bytes;
344 | }
345 |
346 | /**
347 | * Clone Git repository to $target directory
348 | *
349 | * @param $target Target directory
350 | * @param $tag Tag or branch to check out
351 | * @return bool
352 | */
353 | protected function _clone($directory, $tag = false)
354 | {
355 | $repository = $this->versions->getRepository();
356 |
357 | if (!file_exists($directory))
358 | {
359 | $this->output->writeln("Cloning $repository - this could take a few minutes ..");
360 |
361 | $option = strtolower($this->input->getOption('clone'));
362 | $args = $option == 'shallow' ? '--depth 1' : '';
363 |
364 | if (is_string($tag)) {
365 | $args .= sprintf(' --branch %s', escapeshellarg($tag));
366 | }
367 |
368 | $command = sprintf("git clone %s --recursive %s %s", $args, escapeshellarg($repository), escapeshellarg($directory));
369 |
370 | exec($command, $result, $exit_code);
371 |
372 | if ($exit_code > 0) {
373 | return false;
374 | }
375 | }
376 |
377 | if ($this->versions->isBranch($this->version))
378 | {
379 | $this->output->writeln("Fetching latest changes from $repository - this could take a few minutes ..");
380 |
381 | exec(sprintf("git --git-dir %s fetch", escapeshellarg("$directory/.git")), $result, $exit_code);
382 |
383 | if ($exit_code > 0) {
384 | return false;
385 | }
386 | }
387 |
388 | return true;
389 | }
390 |
391 | /**
392 | * Create tarball from cloned Git repository.
393 | *
394 | * @param $source Git repository
395 | * @param $filename Output filename
396 | * @return bool
397 | */
398 | protected function _archive($source, $filename)
399 | {
400 | $repository = $this->versions->getRepository();
401 |
402 | $this->output->writeln("Creating $this->version archive for $repository ..");
403 |
404 | if (substr($filename, -3) == '.gz') {
405 | $filename = substr($filename, 0, -3);
406 | }
407 |
408 | `git --git-dir "$source/.git" archive --prefix=base/ $this->version >"$filename"`;
409 |
410 | // Make sure to include submodules
411 | if (file_exists("$source/.gitmodules"))
412 | {
413 | exec("cd $source && (git submodule foreach) | while read entering path; do echo \$path; done", $result, $return_var);
414 |
415 | if (is_array($result))
416 | {
417 | foreach ($result as $module)
418 | {
419 | $module = trim($module, "'");
420 | $path = "$source/$module";
421 |
422 | $cmd = "cd $path && git archive --prefix=base/$module/ HEAD > /tmp/$module.tar && tar --concatenate --file=\"$filename\" /tmp/$module.tar";
423 | exec($cmd);
424 |
425 | @unlink("/tmp/$module.tar");
426 | }
427 | }
428 | }
429 |
430 | `gzip $filename`;
431 |
432 | return (bool) @filesize("$filename.gz");
433 | }
434 |
435 | /**
436 | * Validate the given gzipped tarball
437 | *
438 | * @param $file
439 | * @return bool
440 | */
441 | protected function _isValidTarball($file)
442 | {
443 | if (!file_exists($file)) {
444 | return false;
445 | }
446 |
447 | $commands = array(
448 | "gunzip -t $file",
449 | "tar -tzf $file >/dev/null"
450 | );
451 |
452 | foreach ($commands as $command)
453 | {
454 | exec($command, $result, $returnVal);
455 |
456 | if ($returnVal != 0) {
457 | return false;
458 | }
459 | }
460 |
461 | return true;
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/Export.php:
--------------------------------------------------------------------------------
1 |
7 | * @link http://github.com/joomlatools/joomlatools-console-backup for the canonical source repository
8 | */
9 |
10 | namespace Joomlatools\Console\Command\Site;
11 |
12 | use Symfony\Component\Console\Input\InputInterface;
13 | use Symfony\Component\Console\Input\InputOption;
14 | use Symfony\Component\Console\Output\OutputInterface;
15 |
16 | use Joomlatools\Console\Command\Database\AbstractDatabase;
17 |
18 | /**
19 | * Backup plugin class.
20 | *
21 | * @author Steven Rombauts
22 | * @package Joomlatools\Console
23 | */
24 | class Export extends AbstractDatabase
25 | {
26 | protected function configure()
27 | {
28 | parent::configure();
29 |
30 | $this->setName('site:export')
31 | ->addOption(
32 | 'folder',
33 | null,
34 | InputOption::VALUE_REQUIRED,
35 | "Target folder where the backup should be stored. Defaults to site folder",
36 | null
37 | )
38 | ->addOption(
39 | 'filename',
40 | null,
41 | InputOption::VALUE_REQUIRED,
42 | "File name for the backup. Defaults to sitename_date.format",
43 | null
44 | )
45 | ->addOption(
46 | 'include-database',
47 | null,
48 | InputOption::VALUE_NEGATABLE,
49 | 'Includes the database contents in the backup',
50 | true
51 | )
52 | ->setDescription('Export site files and database');
53 | }
54 |
55 | protected function execute(InputInterface $input, OutputInterface $output)
56 | {
57 | parent::execute($input, $output);
58 |
59 | $this->check();
60 |
61 | $dbPath = $this->target_dir.'/database.sql';
62 |
63 | if ($input->getOption('include-database')) {
64 | $this->_backupDatabase($dbPath);
65 | }
66 |
67 | $this->backupFiles($input, $output);
68 |
69 | if ($input->getOption('include-database') && \is_file($dbPath)) {
70 | \unlink($dbPath);
71 | }
72 |
73 | return 0;
74 | }
75 |
76 | public function backupFiles(InputInterface $input, OutputInterface $output)
77 | {
78 | $path = $input->getOption('folder') ?? $this->target_dir;
79 | $path .= '/'.($input->getOption('filename') ?? $this->site.'_export_'.date('Y-m-d').'.tar.gz');
80 |
81 | if (\is_file($path)) {
82 | \unlink($path);
83 | }
84 |
85 | exec(sprintf("cd %s && tar -czvf %s *", $this->target_dir, $path));
86 | }
87 |
88 | public function check()
89 | {
90 | if (!file_exists($this->target_dir)) {
91 | throw new \RuntimeException(sprintf('The site %s does not exist', $this->site));
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/Install.php:
--------------------------------------------------------------------------------
1 | setName('site:install')
33 | ->setDescription('Install an existing Joomla codebase. Sets up configuration and installs the database.')
34 | ->addOption(
35 | 'sample-data',
36 | null,
37 | InputOption::VALUE_REQUIRED,
38 | 'Sample data to install (default|blog|brochure|learn|testing)'
39 | )
40 | ->addOption(
41 | 'overwrite',
42 | null,
43 | InputOption::VALUE_NONE,
44 | 'Overwrite configuration.php if it already exists'
45 | )
46 | ->addOption(
47 | 'drop',
48 | 'd',
49 | InputOption::VALUE_NONE,
50 | 'Drop database if it already exists'
51 | )
52 | ->addOption(
53 | 'symlink',
54 | null,
55 | InputOption::VALUE_REQUIRED,
56 | 'A comma separated list of directories to symlink from the projects directory. Use \'all\' to symlink every folder.'
57 | )
58 | ->addOption(
59 | 'projects-dir',
60 | null,
61 | InputOption::VALUE_REQUIRED,
62 | 'Directory where your custom projects reside',
63 | sprintf('%s/Projects', trim(`echo ~`))
64 | )
65 | ->addOption(
66 | 'interactive',
67 | null,
68 | InputOption::VALUE_NONE,
69 | 'Prompt for configuration details'
70 | )
71 | ->addOption(
72 | 'skip-exists-check',
73 | 'e',
74 | InputOption::VALUE_NONE,
75 | 'Do not check if database already exists or not.'
76 | )
77 | ->addOption(
78 | 'skip-create-statement',
79 | null,
80 | InputOption::VALUE_NONE,
81 | 'Do not run the "CREATE IF NOT EXISTS " query. Use this if the user does not have CREATE privileges on the database.'
82 | )
83 | ->addOption(
84 | 'options',
85 | null,
86 | InputOption::VALUE_REQUIRED,
87 | "A YAML file consisting of serialized parameters to override JConfig."
88 | );
89 | }
90 |
91 | protected function execute(InputInterface $input, OutputInterface $output)
92 | {
93 | parent::execute($input, $output);
94 |
95 | if ($input->getOption('interactive')) {
96 | $this->_promptDatabaseDetails($input, $output);
97 | }
98 |
99 | $this->symlink = $input->getOption('symlink');
100 | if (is_string($this->symlink)) {
101 | $this->symlink = explode(',', $this->symlink);
102 | }
103 |
104 | $this->check($input, $output);
105 |
106 | $this->importdb($input, $output);
107 |
108 | $this->createConfig($input, $output);
109 |
110 | if ($this->symlink)
111 | {
112 | $this->symlinkProjects($input, $output);
113 | $this->installExtensions($input, $output);
114 | }
115 |
116 | $output->writeln("Your new Joomla site has been created.");
117 | $output->writeln("It was installed using the domain name $this->site.test.");
118 | $output->writeln("You can login using the following username and password combination: admin/admin.");
119 |
120 |
121 | return 0;
122 | }
123 |
124 | public function check(InputInterface $input, OutputInterface $output)
125 | {
126 | if (!file_exists($this->target_dir)) {
127 | throw new \RuntimeException(sprintf('The site %s does not exist!', $this->site));
128 | }
129 | }
130 |
131 | public function importdb(InputInterface $input, OutputInterface $output)
132 | {
133 | $arguments = array(
134 | 'site:database:install',
135 | 'site' => $this->site,
136 | '--www' => $this->www
137 | );
138 |
139 | $optionalArgs = array('sample-data', 'drop', 'mysql-login', 'mysql-db-prefix', 'mysql-host', 'mysql-port', 'mysql-database', 'skip-exists-check', 'skip-create-statement', 'www', 'use-webroot-dir');
140 | foreach ($optionalArgs as $optionalArg)
141 | {
142 | $value = $input->getOption($optionalArg);
143 | if (!empty($value)) {
144 | $arguments['--' . $optionalArg] = $value;
145 | }
146 | }
147 |
148 | if ($input->getOption('interactive')) {
149 | $arguments['--skip-exists-check'] = true;
150 | }
151 |
152 | $command = new Database\Install();
153 | $command->run(new ArrayInput($arguments), $output);
154 | }
155 |
156 | public function createConfig(InputInterface $input, OutputInterface $output)
157 | {
158 | $arguments = array(
159 | 'site:configure',
160 | 'site' => $this->site,
161 | '--www' => $this->www
162 | );
163 |
164 | $optionalArgs = array('overwrite', 'mysql-login', 'mysql-db-prefix', 'mysql-host', 'mysql-port', 'mysql-database', 'mysql-driver', 'interactive', 'options', 'www', 'use-webroot-dir');
165 | foreach ($optionalArgs as $optionalArg)
166 | {
167 | $value = $input->getOption($optionalArg);
168 | if (!empty($value)) {
169 | $arguments['--' . $optionalArg] = $value;
170 | }
171 | }
172 |
173 | $command = new Configure();
174 | $command->setApplication($this->getApplication());
175 | $command->skipDatabasePrompt();
176 | $command->run(new ArrayInput($arguments), $output);
177 | }
178 |
179 | public function symlinkProjects(InputInterface $input, OutputInterface $output)
180 | {
181 | $symlink_input = new ArrayInput(array(
182 | 'site:symlink',
183 | 'site' => $input->getArgument('site'),
184 | 'symlink' => $this->symlink,
185 | '--www' => $this->www,
186 | '--projects-dir' => $input->getOption('projects-dir')
187 | ));
188 | $symlink = new Command\Extension\Symlink();
189 |
190 | $symlink->run($symlink_input, $output);
191 | }
192 |
193 | public function installExtensions(InputInterface $input, OutputInterface $output)
194 | {
195 | $extension_input = new ArrayInput(array(
196 | 'extension:install',
197 | 'site' => $input->getArgument('site'),
198 | 'extension' => $this->symlink,
199 | '--www' => $this->www
200 | ));
201 | $installer = new Command\Extension\Install();
202 |
203 | $installer->run($extension_input, $output);
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Site/Listing.php:
--------------------------------------------------------------------------------
1 | setName('site:list')
23 | ->setDescription('List Joomla sites')
24 | ->addOption(
25 | 'format',
26 | null,
27 | InputOption::VALUE_OPTIONAL,
28 | 'The output format (txt or json)',
29 | 'txt'
30 | )
31 | ->addOption(
32 | 'www',
33 | null,
34 | InputOption::VALUE_REQUIRED,
35 | "Web server root",
36 | '/var/www'
37 | )
38 | ->setHelp('List Joomla sites running on this server');
39 | }
40 |
41 | protected function execute(InputInterface $input, OutputInterface $output)
42 | {
43 | define('_JEXEC', true);
44 | define('JPATH_BASE', true);
45 | define('JPATH_PLATFORM', true);
46 |
47 | $docroot = $input->getOption('www');
48 |
49 | if (!file_exists($docroot)) {
50 | throw new \RuntimeException(sprintf('Web server root \'%s\' does not exist.', $docroot));
51 | }
52 |
53 | $dir = new \DirectoryIterator($docroot);
54 | $sites = array();
55 |
56 | foreach ($dir as $fileinfo)
57 | {
58 | if ($fileinfo->isDir() && !$fileinfo->isDot())
59 | {
60 | $version = Util::getJoomlaVersion($fileinfo->getPathname());
61 |
62 | if ($version !== false)
63 | {
64 | $sites[] = (object) array(
65 | 'name' => $fileinfo->getFilename(),
66 | 'docroot' => $docroot . '/' . $fileinfo->getFilename() . '/',
67 | 'type' => $version->type == 'joomla-cms-new' ? 'joomla-cms' : $version->type,
68 | 'version' => $version->release
69 | );
70 | }
71 | }
72 | }
73 |
74 | if (!in_array($input->getOption('format'), array('txt', 'json'))) {
75 | throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $input->getOption('format')));
76 | }
77 |
78 | switch ($input->getOption('format'))
79 | {
80 | case 'json':
81 | $result = new \stdClass();
82 | $result->command = $input->getArgument('command');
83 | $result->data = $sites;
84 |
85 | $options = (version_compare(phpversion(),'5.4.0') >= 0 ? JSON_PRETTY_PRINT : 0);
86 | $string = json_encode($result, $options);
87 | break;
88 | case 'txt':
89 | default:
90 | $lines = array();
91 | foreach ($sites as $i => $site) {
92 | $lines[] = sprintf("%s. %s (%s %s)", ($i+1), $site->name, $site->type, $site->version);
93 | }
94 |
95 | $string = implode("\n", $lines);
96 | break;
97 | }
98 |
99 | $output->writeln($string);
100 |
101 | return 0;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Versions.php:
--------------------------------------------------------------------------------
1 | repository) . '/.versions';
41 | }
42 |
43 | $this
44 | ->setName('versions')
45 | ->setDescription('Show available versions in Joomla Git repository')
46 | ->addOption(
47 | 'refresh',
48 | null,
49 | InputOption::VALUE_NONE,
50 | 'Refresh the versions cache'
51 | )
52 | ->addOption(
53 | 'clear-cache',
54 | null,
55 | InputOption::VALUE_NONE,
56 | 'Clear the downloaded files cache'
57 | )
58 | ->addOption(
59 | 'repo',
60 | null,
61 | InputOption::VALUE_REQUIRED,
62 | 'Alternative Git repository to clone. Also accepts a gzipped tar archive instead of a Git repository.',
63 | $this->repository
64 | );
65 | }
66 |
67 | protected function execute(InputInterface $input, OutputInterface $output)
68 | {
69 | $this->setRepository($input->getOption('repo'));
70 |
71 | if ($input->getOption('refresh')) {
72 | $this->refresh();
73 | }
74 |
75 | if ($input->getOption('clear-cache')) {
76 | $this->clearcache($output);
77 | }
78 |
79 | $list = $this->_getVersions();
80 |
81 | foreach($list as $ref => $versions)
82 | {
83 | $chunks = array_chunk($versions, 4);
84 | $header = $ref === 'heads' ? "Branches" : "Releases";
85 |
86 | $table = new Table($output);
87 |
88 | $table->setHeaders(array($header))
89 | ->setRows($chunks)
90 | ->setStyle('compact')
91 | ->render($output);
92 | }
93 |
94 | return 0;
95 | }
96 |
97 | public function setRepository($repository)
98 | {
99 | $this->repository = $repository;
100 |
101 | self::$file = Util::getWritablePath() . '/cache/' . md5($this->repository) . '/.versions';
102 | }
103 |
104 | public function getRepository()
105 | {
106 | return $this->repository;
107 | }
108 |
109 | /**
110 | * Check if the repository is a valid Git repository.
111 | *
112 | * @return bool
113 | */
114 | public function isGitRepository()
115 | {
116 | $cmd = "GIT_SSH_COMMAND=\"ssh -oBatchMode=yes\" GIT_ASKPASS=/bin/echo git ls-remote $this->repository 2>&1";
117 | exec($cmd, $output, $returnVal);
118 |
119 | return $returnVal === 0;
120 | }
121 |
122 | public function getCacheDirectory()
123 | {
124 | $cachedir = dirname(self::$file);
125 |
126 | if (!file_exists($cachedir)) {
127 | mkdir($cachedir, 0755, true);
128 | }
129 |
130 | return $cachedir;
131 | }
132 |
133 | public function clearcache(OutputInterface $output)
134 | {
135 | $cachedir = $this->getCacheDirectory();
136 |
137 | if(!empty($cachedir) && file_exists($cachedir))
138 | {
139 | `rm -rf $cachedir/*.tar.gz`;
140 |
141 | $output->writeln("Downloaded version cache has been cleared.");
142 | }
143 | }
144 |
145 | public function refresh()
146 | {
147 | if (file_exists(self::$file)) {
148 | unlink(self::$file);
149 | }
150 |
151 | $cmd = "GIT_SSH_COMMAND=\"ssh -oBatchMode=yes\" GIT_ASKPASS=/bin/echo git ls-remote $this->repository 2>&1 | grep -E 'refs/(tags|heads)' | grep -v '{}'";
152 | exec($cmd, $refs, $returnVal);
153 |
154 | if ($returnVal != 0) {
155 | $refs = array();
156 | }
157 |
158 | $versions = array('tags' => array(), 'heads' => array());
159 | $pattern = '/^[a-z0-9]+\s+refs\/(heads|tags)\/([a-z0-9\.\-_\/]+)$/im';
160 |
161 | foreach($refs as $ref)
162 | {
163 | if(preg_match($pattern, $ref, $matches))
164 | {
165 | $type = isset($versions[$matches[1]]) ? $versions[$matches[1]] : array();
166 |
167 | if($matches[1] == 'tags')
168 | {
169 | if($matches[2] == '1.7.3' || !preg_match('/^v?\d\.\d+/m', $matches[2])) {
170 | continue;
171 | }
172 | }
173 |
174 | $type[] = $matches[2];
175 | $versions[$matches[1]] = $type;
176 | }
177 | }
178 |
179 | if (!file_exists(dirname(self::$file))) {
180 | mkdir(dirname(self::$file), 0755, true);
181 | }
182 |
183 | file_put_contents(self::$file, json_encode($versions));
184 | }
185 |
186 | protected function _getVersions()
187 | {
188 | if (!file_exists(self::$file)) {
189 | $this->refresh();
190 | }
191 |
192 | $list = json_decode(file_get_contents(self::$file), true);
193 |
194 | if (is_null($list))
195 | {
196 | $this->refresh();
197 | $list = json_decode(file_get_contents(self::$file), true);
198 | }
199 |
200 | $list = array_reverse($list, true);
201 |
202 | return $list;
203 | }
204 |
205 | public function getLatestRelease($prefix = null)
206 | {
207 | $latest = '0.0.0';
208 |
209 | if (!$this->isGitRepository()) {
210 | return 'current';
211 | }
212 |
213 | $versions = $this->_getVersions();
214 |
215 | if (!isset($versions['tags'])) {
216 | return 'master';
217 | }
218 |
219 | $major = $prefix;
220 | if (!is_null($major) && substr($major, 0, 1) == 'v') {
221 | $major = substr($major, 1);
222 | }
223 |
224 | foreach($versions['tags'] as $version)
225 | {
226 | if(!preg_match('/v?\d\.\d+\.\d+.*/im', $version) || preg_match('#(?:alpha|beta|rc)#i', $version)) {
227 | continue;
228 | }
229 |
230 | $compare = $version;
231 | if (substr($compare, 0, 1) == 'v') {
232 | $compare = substr($compare, 1);
233 | }
234 |
235 | if(!is_null($major) && substr($compare, 0, strlen($major)) != $major) {
236 | continue;
237 | }
238 |
239 | if(version_compare($latest, strtolower($compare), '<')) {
240 | $latest = $version;
241 | }
242 | }
243 |
244 | if ($latest == '0.0.0') {
245 | $latest = 'master';
246 | }
247 |
248 | return $latest;
249 | }
250 |
251 | public function isBranch($version)
252 | {
253 | $versions = $this->_getVersions();
254 |
255 | return in_array($version, $versions['heads']);
256 | }
257 |
258 | public function isTag($version)
259 | {
260 | $versions = $this->_getVersions();
261 |
262 | return in_array($version, $versions['tags']);
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Vhost/Create.php:
--------------------------------------------------------------------------------
1 | setName('vhost:create')
24 | ->setDescription('Creates a new Apache2 virtual host')
25 | ->addOption(
26 | 'http-port',
27 | null,
28 | InputOption::VALUE_REQUIRED,
29 | 'The HTTP port the virtual host should listen to',
30 | 80
31 | )
32 | ->addOption(
33 | 'ssl-port',
34 | null,
35 | InputOption::VALUE_REQUIRED,
36 | 'The HTTPS port the virtual host should listen to',
37 | 443
38 | )
39 | ->addOption(
40 | 'template',
41 | null,
42 | InputOption::VALUE_REQUIRED,
43 | 'Custom file to use as the Apache vhost configuration. Make sure to include HTTP and SSL directives if you need both.',
44 | null
45 | )
46 | ->addOption('folder',
47 | null,
48 | InputOption::VALUE_REQUIRED,
49 | 'The Apache2 vhost folder',
50 | '/etc/apache2/sites-enabled'
51 | )
52 | ->addOption('filename',
53 | null,
54 | InputOption::VALUE_OPTIONAL,
55 | 'The Apache2 vhost file name',
56 | null,
57 | )
58 | ->addOption('restart-command',
59 | null,
60 | InputOption::VALUE_OPTIONAL,
61 | 'The full command for restarting Apache2',
62 | null
63 | )
64 | ;
65 | }
66 |
67 | protected function execute(InputInterface $input, OutputInterface $output)
68 | {
69 | parent::execute($input, $output);
70 |
71 | if (!file_exists($this->target_dir)) {
72 | throw new \RuntimeException(sprintf('Site not found: %s', $this->site));
73 | }
74 |
75 | $target = $this->_getVhostPath($input);
76 |
77 | $variables = $this->_getVariables($input);
78 |
79 | if (!is_dir(dirname($target))) {
80 | mkdir(dirname($target), 0755, true);
81 | }
82 |
83 | if (is_dir(dirname($target)))
84 | {
85 | $template = $this->_getTemplate($input);
86 | $template = str_replace(array_keys($variables), array_values($variables), $template);
87 |
88 | file_put_contents($target, $template);
89 |
90 | if ($command = $input->getOption('restart-command')) {
91 | `$command`;
92 | }
93 | }
94 |
95 | return 0;
96 | }
97 |
98 | protected function _getVhostPath($input)
99 | {
100 | $folder = str_replace('[site]', $this->site, $input->getOption('folder'));
101 | $file = $input->getOption('filename') ?? $input->getArgument('site').'.conf';
102 |
103 | return $folder.'/'.$file;
104 | }
105 |
106 | protected function _getVariables(InputInterface $input)
107 | {
108 | $documentroot = $this->target_dir;
109 |
110 | $variables = array(
111 | '%site%' => $input->getArgument('site'),
112 | '%root%' => $documentroot,
113 | '%http_port%' => $input->getOption('http-port'),
114 | '%ssl_port%' => $input->getOption('ssl-port'),
115 | );
116 |
117 | return $variables;
118 | }
119 |
120 | protected function _getTemplate(InputInterface $input)
121 | {
122 | if ($template = $input->getOption('template'))
123 | {
124 | if (file_exists($template))
125 | {
126 | $file = basename($template);
127 | $path = dirname($template);
128 | }
129 | else throw new \Exception(sprintf('Template file %s does not exist.', $template));
130 | }
131 | else
132 | {
133 | $path = realpath(__DIR__.'/../../../../../bin/.files/vhosts');
134 |
135 | $file = 'apache.conf';
136 | }
137 |
138 | $template = file_get_contents(sprintf('%s/%s', $path, $file));
139 |
140 | return $template;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Command/Vhost/Remove.php:
--------------------------------------------------------------------------------
1 | setName('vhost:remove')
24 | ->setDescription('Removes the Apache2 virtual host')
25 | ->addOption('folder',
26 | null,
27 | InputOption::VALUE_REQUIRED,
28 | 'The Apache2 vhost folder',
29 | '/etc/apache2/sites-enabled'
30 | )
31 | ->addOption('filename',
32 | null,
33 | InputOption::VALUE_OPTIONAL,
34 | 'The Apache2 vhost file name',
35 | null,
36 | )
37 | ->addOption('restart-command',
38 | null,
39 | InputOption::VALUE_OPTIONAL,
40 | 'The full command for restarting Apache2',
41 | null
42 | )
43 | ;
44 | }
45 |
46 | protected function execute(InputInterface $input, OutputInterface $output)
47 | {
48 | parent::execute($input, $output);
49 |
50 | $file = $this->_getVhostPath($input);
51 |
52 | if (is_file($file))
53 | {
54 | $this->_runWithOrWithoutSudo("rm -f $file");
55 |
56 | if ($command = $input->getOption('restart-command')) {
57 | $this->_runWithOrWithoutSudo($command);
58 | }
59 | }
60 |
61 | return 0;
62 | }
63 |
64 | protected function _getVhostPath($input)
65 | {
66 | $folder = str_replace('[site]', $this->site, $input->getOption('folder'));
67 | $file = $input->getOption('filename') ?? $input->getArgument('site').'.conf';
68 |
69 | return $folder.'/'.$file;
70 | }
71 |
72 | protected function _runWithOrWithoutSudo($command)
73 | {
74 | $hasSudo = `which sudo`;
75 |
76 | if ($hasSudo) {
77 | `sudo $command`;
78 | } else {
79 | `$command`;
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Joomla/Application.php:
--------------------------------------------------------------------------------
1 |
22 | * @package Joomlatools\Composer
23 | */
24 | class Application extends JApplicationCli
25 | {
26 | protected $_clientId = Bootstrapper::CLI;
27 | protected $_application = null;
28 | protected $_messageQueue = array();
29 | protected $_options = array();
30 |
31 | /**
32 | * Class constructor.
33 | *
34 | * @param array $options An array of configuration settings.
35 | * @param mixed $input An optional argument to provide dependency injection for the application's
36 | * input object.
37 | * @param mixed $config An optional argument to provide dependency injection for the application's
38 | * config object.
39 | * @param mixed $dispatcher An optional argument to provide dependency injection for the application's
40 | * event dispatcher.
41 | * @return void
42 | *
43 | * @see JApplicationCli
44 | */
45 | public function __construct($options = array(), JInputCli $input = null, JRegistry $config = null, JDispatcher $dispatcher = null)
46 | {
47 | $this->_options = $options;
48 |
49 | parent::__construct($input, $config, $dispatcher);
50 |
51 | if (isset($this->_options['client_id'])) {
52 | $this->_clientId = $this->_options['client_id'];
53 | }
54 |
55 | $this->_initialize();
56 | }
57 |
58 | /**
59 | * Initialise the application.
60 | *
61 | * Loads the necessary Joomla libraries to make sure
62 | * the Joomla application can run and sets up the JFactory properties.
63 | *
64 | * @param array $options An optional associative array of configuration settings.
65 | * @return void
66 | */
67 | protected function _initialize()
68 | {
69 | // Load dependencies
70 | jimport('joomla.application.component.helper');
71 | jimport('joomla.application.menu');
72 |
73 | jimport('joomla.environment.uri');
74 |
75 | jimport('joomla.event.dispatcher');
76 |
77 | jimport('joomla.utilities.utility');
78 | jimport('joomla.utilities.arrayhelper');
79 |
80 | jimport('joomla.application.module.helper');
81 |
82 | // Tell JFactory where to find the current application object
83 | JFactory::$application = $this;
84 |
85 | // Start a new session and tell JFactory where to find it if we're on Joomla 3
86 | if(version_compare(JVERSION, '3.0.0', '>=')) {
87 | JFactory::$session = $this->_startSession();
88 | }
89 |
90 | // Load plugins
91 | JPluginHelper::importPlugin('system');
92 |
93 | // Load required languages
94 | $lang = JFactory::getLanguage();
95 | $lang->load('lib_joomla', JPATH_ADMINISTRATOR, null, true);
96 | $lang->load('com_installer', JPATH_ADMINISTRATOR, null, true);
97 |
98 | // Instiantiate the Joomla application object if we
99 | // need either admin or site
100 | $name = $this->getName();
101 | if (in_array($name, array('administrator', 'site'))) {
102 | $this->_application = \JApplicationCms::getInstance($name);
103 | }
104 | }
105 |
106 | /**
107 | * Authenticates the Joomla user.
108 | *
109 | * This method will load the default user object and change its guest status to logged in.
110 | * It will then simply copy all the properties defined by key in the $credentials argument
111 | * onto this JUser object, allowing you to completely overwrite the user information.
112 | *
113 | * @param array $credentials Associative array containing user object properties.
114 | *
115 | * @return void
116 | */
117 | public function authenticate($credentials)
118 | {
119 | $user = JFactory::getUser();
120 | $user->guest = 0;
121 |
122 | foreach($credentials as $key => $value) {
123 | $user->$key = $value;
124 | }
125 |
126 | // Push the JUser object into the session otherwise getUser() always returns a new instance of JUser.
127 | JFactory::getSession()->set('user', $user);
128 | }
129 |
130 | /**
131 | * Checks if this Joomla installation has a certain element installed.
132 | *
133 | * @param string $element The name of the element
134 | * @param string $type The type of extension
135 | *
136 | * @return bool
137 | */
138 | public function hasExtension($element, $type = 'component')
139 | {
140 | $db = JFactory::getDbo();
141 | $sql = "SELECT `extension_id`, `state` FROM `#__extensions`"
142 | ." WHERE `element` = ".$db->quote($element)." AND `type` = ".$db->quote($type);
143 |
144 | $extension = $db->setQuery($sql)->loadObject();
145 |
146 | return ($extension && $extension->state != -1);
147 | }
148 |
149 | /**
150 | * Installs an extension from the given path.
151 | *
152 | * @param string $path Path to the extracted installation package.
153 | *
154 | * @return bool
155 | */
156 | public function install($path)
157 | {
158 | $installer = $this->getInstaller();
159 |
160 | return $installer->install($path);
161 | }
162 |
163 | /**
164 | * Updates an existing extension from the given path.
165 | *
166 | * @param string $path Path to the extracted installation package.
167 | *
168 | * @return bool
169 | */
170 | public function update($path)
171 | {
172 | $installer = $this->getInstaller();
173 |
174 | return $installer->update($path);
175 | }
176 |
177 | /**
178 | * Retrieves value from the Joomla configuration.
179 | *
180 | * @param string $varname Name of the configuration property
181 | * @param mixed $default Default value
182 | *
183 | * @return mixed
184 | */
185 | public function getCfg($varname, $default = null)
186 | {
187 | return JFactory::getConfig()->get($varname, $default);
188 | }
189 |
190 | /**
191 | * Enqueue flash message.
192 | *
193 | * @param string $msg The message
194 | * @param string $type Type of message (can be message/notice/error)
195 | *
196 | * @return void
197 | */
198 | public function enqueueMessage($msg, $type = 'message')
199 | {
200 | $this->_messageQueue[] = array('message' => $msg, 'type' => strtolower($type));
201 | }
202 |
203 | /**
204 | * Return all currently enqueued flash messages.
205 | *
206 | * @return array
207 | */
208 | public function getMessageQueue()
209 | {
210 | return $this->_messageQueue;
211 | }
212 |
213 | /**
214 | * Get the JInstaller object.
215 | *
216 | * @return JInstaller
217 | */
218 | public function getInstaller()
219 | {
220 | // @TODO keep one instance available per install package
221 | // instead of instantiating a new object each time.
222 | // Re-using the same instance for multiple installations will fail.
223 | return new JInstaller();
224 | }
225 |
226 | public function getPath()
227 | {
228 | return JPATH_ROOT;
229 | }
230 |
231 | /**
232 | * Get the current template name.
233 | * Always return 'system' as template.
234 | *
235 | * @return string
236 | */
237 | public function getTemplate()
238 | {
239 | return 'system';
240 | }
241 |
242 | /**
243 | * Gets the client id of the current running application.
244 | *
245 | * @return integer A client identifier.
246 | */
247 | public function getClientId()
248 | {
249 | return $this->_clientId;
250 | }
251 |
252 | /**
253 | * Get the current application name.
254 | *
255 | * @return string
256 | */
257 | public function getName()
258 | {
259 | $name = '';
260 |
261 | switch ($this->_clientId)
262 | {
263 | case Bootstrapper::SITE:
264 | $name = 'site';
265 | break;
266 | case Bootstrapper::ADMIN:
267 | $name = 'administrator';
268 | break;
269 | default:
270 | $name = 'cli';
271 | break;
272 | }
273 |
274 | return $name;
275 | }
276 |
277 | /**
278 | * Checks if interface is site or not.
279 | *
280 | * @return bool
281 | */
282 | public function isSite()
283 | {
284 | return $this->_clientId == Bootstrapper::SITE;
285 | }
286 |
287 | /**
288 | * Checks if interface is admin or not.
289 | *
290 | * @return bool
291 | */
292 | public function isAdmin()
293 | {
294 | return $this->_clientId == Bootstrapper::ADMIN;
295 | }
296 |
297 | /**
298 | * @param $identifier
299 | * @return bool
300 | */
301 | public function isClient($identifier)
302 | {
303 | return $this->getName() === $identifier;
304 | }
305 |
306 | /**
307 | * Determine if we are using a secure (SSL) connection.
308 | *
309 | * @return boolean True if using SSL, false if not.
310 | */
311 | public function isSSLConnection()
312 | {
313 | return false;
314 | }
315 |
316 | /**
317 | * Stub for flushAssets() method.
318 | */
319 | public function flushAssets()
320 | {
321 | }
322 |
323 | /**
324 | * Return a reference to the JRouter object.
325 | *
326 | * @param string $name The name of the application.
327 | * @param array $options An optional associative array of configuration settings.
328 | *
329 | * @return JRouter
330 | */
331 | public static function getRouter($name = null, array $options = array())
332 | {
333 | if (!isset($name))
334 | {
335 | $app = \JFactory::getApplication();
336 | $name = $app->getName();
337 | }
338 |
339 | try
340 | {
341 | $router = \JRouter::getInstance($name, $options);
342 | }
343 | catch (\Exception $e)
344 | {
345 | return null;
346 | }
347 |
348 | return $router;
349 | }
350 |
351 | /**
352 | * Method to load a PHP configuration class file based on convention and return the instantiated data object. You
353 | * will extend this method in child classes to provide configuration data from whatever data source is relevant
354 | * for your specific application.
355 | * Additionally injects the root_user into the configuration.
356 | *
357 | * @param string $file The path and filename of the configuration file. If not provided, configuration.php
358 | * in JPATH_BASE will be used.
359 | * @param string $class The class name to instantiate.
360 | *
361 | * @return mixed Either an array or object to be loaded into the configuration object.
362 | *
363 | * @since 11.1
364 | */
365 | protected function fetchConfigurationData($file = '', $class = 'JConfig')
366 | {
367 | $config = parent::fetchConfigurationData($file, $class);
368 |
369 | // Inject the root user configuration
370 | if(isset($this->_options['root_user']))
371 | {
372 | $root_user = isset($this->_options['root_user']) ? $this->_options['root_user'] : 'root';
373 |
374 | if (is_array($config)) {
375 | $config['root_user'] = $root_user;
376 | }
377 | elseif (is_object($config)) {
378 | $config->root_user = $root_user;
379 | }
380 | }
381 |
382 | return $config;
383 | }
384 |
385 | /**
386 | * Creates a new Joomla session.
387 | *
388 | * @return JSession
389 | */
390 | protected function _startSession()
391 | {
392 | $name = md5($this->getCfg('secret') . get_class($this));
393 | $lifetime = $this->getCfg('lifetime') * 60 ;
394 | $handler = $this->getCfg('session_handler', 'none');
395 |
396 | $options = array(
397 | 'name' => $name,
398 | 'expire' => $lifetime
399 | );
400 |
401 | $session = JSession::getInstance($handler, $options);
402 | $session->initialise($this->input, $this->dispatcher);
403 |
404 | if ($session->getState() == 'expired') {
405 | $session->restart();
406 | } else {
407 | $session->start();
408 | }
409 |
410 | return $session;
411 | }
412 |
413 | /**
414 | * Load an object or array into the application configuration object.
415 | *
416 | * @param mixed $data Either an array or object to be loaded into the configuration object.
417 | *
418 | * @return Application Instance of $this
419 | */
420 | public function loadConfiguration($data)
421 | {
422 | parent::loadConfiguration($data);
423 |
424 | JFactory::$config = $this->config;
425 |
426 | return $this;
427 | }
428 |
429 | /**
430 | * Gets a user state.
431 | *
432 | * @param string $key The path of the state.
433 | * @param mixed $default Optional default value, returned if the internal value is null.
434 | *
435 | * @return mixed The user state or null.
436 | *
437 | * @since 11.1
438 | */
439 | public function getUserState($key, $default = null)
440 | {
441 | $session = JFactory::getSession();
442 | $registry = $session->get('registry');
443 |
444 | if (!is_null($registry))
445 | {
446 | return $registry->get($key, $default);
447 | }
448 |
449 | return $default;
450 | }
451 |
452 | /**
453 | * Sets the value of a user state variable.
454 | *
455 | * @param string $key The path of the state.
456 | * @param string $value The value of the variable.
457 | *
458 | * @return mixed The previous state, if one existed.
459 | *
460 | * @since 11.1
461 | */
462 | public function setUserState($key, $value)
463 | {
464 | $session = JFactory::getSession();
465 | $registry = $session->get('registry');
466 |
467 | if (!is_null($registry))
468 | {
469 | return $registry->set($key, $value);
470 | }
471 |
472 | return null;
473 | }
474 |
475 | /**
476 | * Gets the value of a user state variable.
477 | *
478 | * @param string $key The key of the user state variable.
479 | * @param string $request The name of the variable passed in a request.
480 | * @param string $default The default value for the variable if not found. Optional.
481 | * @param string $type Filter for the variable, for valid values see {@link JFilterInput::clean()}. Optional.
482 | *
483 | * @return The request user state.
484 | *
485 | * @since 11.1
486 | */
487 | public function getUserStateFromRequest($key, $request, $default = null, $type = 'none')
488 | {
489 | $cur_state = $this->getUserState($key, $default);
490 | $new_state = \JRequest::getVar($request, null, 'default', $type);
491 |
492 | // Save the new value only if it was set in this request.
493 | if ($new_state !== null)
494 | {
495 | $this->setUserState($key, $new_state);
496 | }
497 | else
498 | {
499 | $new_state = $cur_state;
500 | }
501 |
502 | return $new_state;
503 | }
504 |
505 | /**
506 | * Just a stub to catch anything that calls $app->redirect(), expecting us to be JApplication,
507 | * rather than JApplicationCLI, such as installer code run via extension:install, so it doesn't
508 | * drop dead from a fatal PHP error.
509 | *
510 | * @param string $url does nothing
511 | * @param boolean $moved does nothing
512 | *
513 | * @return void
514 | */
515 | public function redirect($url, $moved = false)
516 | {
517 | /**
518 | * Throw an exception, to short circuit whatever code called us, as the J! redirect()
519 | * would usually close() and go no further, so we don't want to just return.
520 | * We can then catch this exception in (for instance) ExtensionInstallFile, and
521 | * go about our business.
522 | */
523 | throw new \RuntimeException(sprintf('Application tried to redirect to %s', $url));
524 | }
525 |
526 | /**
527 | * Returns the application JMenu object.
528 | *
529 | * @param string $name The name of the application/client.
530 | * @param array $options An optional associative array of configuration settings.
531 | *
532 | * @return JMenu
533 | */
534 | public function getMenu($name = null, $options = array())
535 | {
536 | if (!isset($name))
537 | {
538 | $app = JFactory::getApplication();
539 | $name = $app->getName();
540 | }
541 |
542 | try
543 | {
544 | if (!class_exists('JMenu' . ucfirst($name))) {
545 | jimport('cms.menu.'.strtolower($name));
546 | }
547 |
548 | $menu = JMenu::getInstance($name, $options);
549 | }
550 | catch (Exception $e) {
551 | return null;
552 | }
553 |
554 | return $menu;
555 | }
556 |
557 | /**
558 | * Forward m
559 | *
560 | * @param $method
561 | * @param $args
562 | * @return mixed
563 | * @throws Exception
564 | */
565 | public function __call($method, $args)
566 | {
567 | if (!method_exists($this, $method) && is_object($this->_application)) {
568 | return call_user_func_array(array($this->_application, $method), $args);
569 | }
570 |
571 | return parent::__call($method, $args);
572 | }
573 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Joomla/Bootstrapper.php:
--------------------------------------------------------------------------------
1 | 'root',
36 | 'client_id' => $client_id
37 | );
38 |
39 | self::$_application = new Application($options);
40 |
41 | $credentials = array(
42 | 'name' => 'root',
43 | 'username' => 'root',
44 | 'groups' => array(8),
45 | 'email' => 'root@localhost.home'
46 | );
47 |
48 | self::$_application->authenticate($credentials);
49 |
50 | // If there are no marks in JProfiler debug plugin performs a division by zero using count($marks)
51 | \JProfiler::getInstance('Application')->mark('Hello world');
52 | }
53 |
54 | return self::$_application;
55 | }
56 |
57 | /**
58 | * Load the Joomla application files
59 | *
60 | * @param $base
61 | */
62 | public static function bootstrap($base)
63 | {
64 | if (!class_exists('\\JApplicationCli'))
65 | {
66 | $_SERVER['HTTP_HOST'] = 'localhost';
67 | $_SERVER['HTTP_USER_AGENT'] = 'joomlatools-console/' . \Joomlatools\Console\Application::VERSION;
68 |
69 | if (!defined('_JEXEC')) {
70 | define('_JEXEC', 1);
71 | }
72 |
73 | if (!defined('DS')) {
74 | define('DS', DIRECTORY_SEPARATOR);
75 | }
76 |
77 | define('JPATH_BASE', realpath($base));
78 |
79 | require_once JPATH_BASE . '/includes/defines.php';
80 | require_once JPATH_BASE . '/includes/framework.php';
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Joomla/Cache.php:
--------------------------------------------------------------------------------
1 | $client ? JPATH_ADMINISTRATOR . '/cache' : JPATH_CACHE
20 | );
21 |
22 | return \JCache::getInstance('', $options)->getAll();
23 | }
24 |
25 | public static function clear($client, array $group = array())
26 | {
27 | if (!self::_isBootstrapped()) {
28 | throw new \RuntimeException('Joomla application has not been bootstrapped');
29 | }
30 |
31 | $group = array_filter($group);
32 |
33 | $options = array(
34 | 'cachebase' => $client ? JPATH_ADMINISTRATOR . '/cache' : JPATH_CACHE
35 | );
36 |
37 | $cache = \JCache::getInstance('', $options);
38 |
39 | if (!count($group)) {
40 | $group = $cache->getAll();
41 | }
42 |
43 | $cleared = array();
44 |
45 | foreach($group as $item)
46 | {
47 | $cache_item = isset($item->group) ? $item->group : $item;
48 | $result = $cache->clean($cache_item);
49 |
50 | if($result) {
51 | $cleared[] = $cache_item;
52 | }
53 | }
54 |
55 | return $cleared;
56 | }
57 |
58 | public static function purge()
59 | {
60 | if (!self::_isBootstrapped()) {
61 | throw new \RuntimeException('Joomla application has not been bootstrapped');
62 | }
63 |
64 | \JFactory::getCache()->gc();
65 |
66 | return true;
67 | }
68 |
69 | protected static function _isBootstrapped()
70 | {
71 | return class_exists('JFactory') && defined('JPATH_BASE');
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Joomla/Util.php:
--------------------------------------------------------------------------------
1 | The last line of output: ".
27 | $command
28 | );
29 | }
30 |
31 | return implode(PHP_EOL, $output);
32 | }
33 |
34 | public static function isJoomla4($base): bool
35 | {
36 | return (bool) \version_compare(static::getJoomlaVersion($base)->release, '4.0.0', '>=');
37 | }
38 |
39 | public static function executeJ4CliCommand($base, $command): string
40 | {
41 | return static::executeCommand("php $base/cli/joomla.php $command");
42 | }
43 |
44 | /**
45 | * Retrieve the Joomla version.
46 | *
47 | * Returns an object with properties type and release.
48 | * Returns FALSE if unable to find correct version.
49 | *
50 | * @param string $base Base path for the Joomla installation
51 | * @return stdclass|boolean
52 | */
53 | public static function getJoomlaVersion($base)
54 | {
55 | $key = md5($base);
56 |
57 | if (!isset(self::$_versions[$key]))
58 | {
59 | $canonical = function($version) {
60 | if (isset($version->RELEASE)) {
61 | return 'v' . $version->RELEASE . '.' . $version->DEV_LEVEL;
62 | }
63 |
64 | // Joomla 3.5 and up uses constants instead of properties in JVersion
65 | $className = get_class($version);
66 | if (defined("$className::RELEASE")) {
67 | return $version::RELEASE . '.' . $version::DEV_LEVEL;
68 | }
69 |
70 | //start to provide support for Joomla 4 onwards
71 | if (defined( "$className::MAJOR_VERSION") && in_array($version::MAJOR_VERSION, ['4', '5'])){
72 | return $version::MAJOR_VERSION . "." . $version::MINOR_VERSION . "." . $version::PATCH_VERSION . ($version::EXTRA_VERSION ? "." . $version::EXTRA_VERSION : '');
73 | }
74 |
75 | return 'unknown';
76 | };
77 |
78 | $files = array(
79 | 'joomla-cms' => '/libraries/cms/version/version.php',
80 | 'joomla-cms-new' => '/libraries/src/Version.php', // 3.8+
81 | 'joomla-1.5' => '/libraries/joomla/version.php'
82 | );
83 |
84 | $code = false;
85 | $application = false;
86 | foreach ($files as $type => $file)
87 | {
88 | $path = $base . $file;
89 |
90 | if (file_exists($path))
91 | {
92 | $code = $path;
93 | $application = $type;
94 |
95 | break;
96 | }
97 | }
98 |
99 | if ($code !== false)
100 | {
101 | if (!defined('JPATH_PLATFORM')) {
102 | define('JPATH_PLATFORM', self::buildTargetPath('/libraries', $base));
103 | }
104 |
105 | if (!defined('_JEXEC')) {
106 | define('_JEXEC', 1);
107 | }
108 |
109 | $identifier = uniqid();
110 |
111 | $source = file_get_contents($code);
112 | $source = preg_replace('/<\?php/', '', $source, 1);
113 |
114 | $pattern = $application == 'joomla-cms-new' ? '/class Version/i' : '/class JVersion/i';
115 | $replacement = $application == 'joomla-cms-new' ? 'class Version' . $identifier : 'class JVersion' . $identifier;
116 |
117 | $source = preg_replace($pattern, $replacement, $source);
118 |
119 | eval($source);
120 |
121 | $class = $application == 'joomla-cms-new' ? '\\Joomla\\CMS\\Version'.$identifier : 'JVersion'.$identifier;
122 | $version = new $class();
123 |
124 | self::$_versions[$key] = (object) array('release' => $canonical($version), 'type' => $application);
125 | }
126 | else self::$_versions[$key] = false;
127 | }
128 |
129 | return self::$_versions[$key];
130 | }
131 |
132 | /**
133 | * Builds the full path for a given path inside a Joomla project.
134 | *
135 | * @param string $path The original relative path to the file/directory
136 | * @param string $base The root directory of the Joomla installation
137 | * @return string Target path
138 | */
139 | public static function buildTargetPath($path, $base = '')
140 | {
141 | if (!empty($base) && substr($base, -1) == '/') {
142 | $base = substr($base, 0, -1);
143 | }
144 |
145 | $path = str_replace($base, '', $path);
146 |
147 | if (substr($path, 0, 1) != '/') {
148 | $path = '/'.$path;
149 | }
150 |
151 | return $base.$path;
152 | }
153 |
154 | /**
155 | * Return a writable path
156 | *
157 | * @return string
158 | */
159 | public static function getWritablePath()
160 | {
161 | $path = \Phar::running();
162 |
163 | if (!empty($path)) {
164 | return sys_get_temp_dir() . '/.joomla';
165 | }
166 |
167 | return self::getTemplatePath();
168 | }
169 |
170 | /**
171 | * Get template directory path
172 | *
173 | * @return string
174 | */
175 | public static function getTemplatePath()
176 | {
177 | $path = \Phar::running();
178 |
179 | if (!empty($path)) {
180 | return $path . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . '.files';
181 | }
182 |
183 | $root = dirname(dirname(dirname(dirname(__DIR__))));
184 |
185 | return realpath($root .DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . '.files');
186 | }
187 | }
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Symlinkers/joomlatools-components.php:
--------------------------------------------------------------------------------
1 | extra) || !isset($manifest->extra->{'joomlatools-component'})) {
23 | return false;
24 | }
25 |
26 | $component = $manifest->extra->{'joomlatools-component'};
27 | $code_folder = Util::buildTargetPath('/libraries/joomlatools-components', $destination);
28 |
29 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
30 | $output->writeln("Symlinking `$component` into `$destination`");
31 | }
32 |
33 | $dirs = array(
34 | $code_folder,
35 | Util::buildTargetPath('/media/koowa', $destination)
36 | );
37 |
38 | foreach ($dirs as $dir)
39 | {
40 | if (!is_dir($dir))
41 | {
42 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
43 | $output->writeln(" * creating empty directory `$dir`");
44 | }
45 |
46 | mkdir($dir, 0755, true);
47 | }
48 | }
49 |
50 | $from = $project;
51 |
52 | if (is_dir($project.'/code')) {
53 | $from = $project.'/code';
54 | }
55 |
56 | $code_destination = $code_folder.'/'.$component;
57 |
58 | if (!file_exists($code_destination))
59 | {
60 | $project = Extension\Symlink::buildSymlinkPath($project, $code_destination);
61 |
62 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
63 | $output->writeln(" * creating link `$code_destination` -> $project");
64 | }
65 |
66 | `ln -sf $from $code_destination`;
67 | }
68 |
69 | // Media folder always has com_ prefix
70 | if (substr($component, 0, 4) !== 'com_') {
71 | $component = 'com_'.$component;
72 | }
73 |
74 | // Special treatment for media files
75 | $media = $project.'/resources/assets';
76 | $target = Util::buildTargetPath('/media/koowa/'.$component, $destination);
77 |
78 | if (is_dir($media) && !file_exists($target))
79 | {
80 | $media = Extension\Symlink::buildSymlinkPath($media, $target);
81 |
82 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
83 | $output->writeln(" * creating link `$target` -> $media");
84 | }
85 |
86 | `ln -sf $media $target`;
87 | }
88 |
89 | return true;
90 | });
--------------------------------------------------------------------------------
/src/Joomlatools/Console/Symlinkers/joomlatools-framework.php:
--------------------------------------------------------------------------------
1 | name) || $manifest->name != 'joomlatools/framework') {
23 | return false;
24 | }
25 |
26 | // build the folders to symlink into
27 | $dirs = array(
28 | Util::buildTargetPath('/media/koowa', $destination),
29 | Util::buildTargetPath('/libraries/joomlatools-components', $destination)
30 | );
31 |
32 | foreach ($dirs as $dir)
33 | {
34 | if (!is_dir($dir))
35 | {
36 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
37 | $output->writeln(" * creating empty directory `$dir`");
38 | }
39 |
40 | mkdir($dir, 0755, true);
41 | }
42 | }
43 |
44 | /*
45 | * Special treatment for media files
46 | */
47 | $media = array(
48 | $project.'/code/libraries/joomlatools/component/koowa/resources/assets' => Util::buildTargetPath('/media/koowa/com_koowa', $destination),
49 | $project.'/code/libraries/joomlatools/library/resources/assets' => Util::buildTargetPath('/media/koowa/framework', $destination),
50 | );
51 |
52 | foreach ($media as $from => $to)
53 | {
54 | if (is_dir($from) && !file_exists($to))
55 | {
56 | $from = Extension\Symlink::buildSymlinkPath($from, $to);
57 |
58 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
59 | $output->writeln(" * creating link `$to` -> $from");
60 | }
61 |
62 | `ln -sf $from $to`;
63 | }
64 | }
65 |
66 | // Component assets
67 | $results = glob($project.'/code/libraries/joomlatools-components/*/resources/assets', GLOB_ONLYDIR);
68 |
69 | foreach ($results as $from)
70 | {
71 | $component = preg_replace('#^.*?joomlatools-components/([^/]+)/resources/assets#', '$1', $from);
72 | $to = Util::buildTargetPath('/media/koowa/com_'.$component, $destination);
73 |
74 | if (!$component || is_link($to)) {
75 | continue;
76 | }
77 |
78 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
79 | $output->writeln(" * creating link `$to` -> $from");
80 | }
81 |
82 | `ln -sf $from $to`;
83 | }
84 |
85 | // Let the default symlinker handle the rest
86 | return false;
87 | });
--------------------------------------------------------------------------------