├── .gitattributes
├── .gitignore
├── LICENSE.txt
├── README.md
├── clitools
├── all
│ ├── README.md
│ ├── all
│ └── all.ps1
└── phpStormSourceHandling.php
├── composer.json
├── composer.lock
├── linklib
└── include.php
├── package-lock.json
├── package.json
├── phing
├── README.md
├── bin
│ └── images
│ │ ├── annot-close.png
│ │ ├── annot-open.png
│ │ ├── blank.png
│ │ ├── callouts
│ │ ├── 1.png
│ │ ├── 1.svg
│ │ ├── 10.png
│ │ ├── 10.svg
│ │ ├── 11.png
│ │ ├── 11.svg
│ │ ├── 12.png
│ │ ├── 12.svg
│ │ ├── 13.png
│ │ ├── 13.svg
│ │ ├── 14.png
│ │ ├── 14.svg
│ │ ├── 15.png
│ │ ├── 15.svg
│ │ ├── 16.svg
│ │ ├── 17.svg
│ │ ├── 18.svg
│ │ ├── 19.svg
│ │ ├── 2.png
│ │ ├── 2.svg
│ │ ├── 20.svg
│ │ ├── 21.svg
│ │ ├── 22.svg
│ │ ├── 23.svg
│ │ ├── 24.svg
│ │ ├── 25.svg
│ │ ├── 26.svg
│ │ ├── 27.svg
│ │ ├── 28.svg
│ │ ├── 29.svg
│ │ ├── 3.png
│ │ ├── 3.svg
│ │ ├── 30.svg
│ │ ├── 4.png
│ │ ├── 4.svg
│ │ ├── 5.png
│ │ ├── 5.svg
│ │ ├── 6.png
│ │ ├── 6.svg
│ │ ├── 7.png
│ │ ├── 7.svg
│ │ ├── 8.png
│ │ ├── 8.svg
│ │ ├── 9.png
│ │ ├── 9.svg
│ │ └── Thumbs.db
│ │ ├── draft.png
│ │ ├── home.png
│ │ ├── important.png
│ │ ├── next.png
│ │ ├── note.png
│ │ ├── prev.png
│ │ ├── tip.png
│ │ ├── toc-blank.png
│ │ ├── toc-minus.png
│ │ ├── toc-plus.png
│ │ ├── up.png
│ │ └── warning.png
├── common.xml
├── default.properties
├── epub
│ ├── com.apple.ibooks.display-options.xml
│ ├── docbook-epub.css
│ └── mimetype
├── release.json
└── tasks
│ ├── AutoVersionTask.php
│ ├── BladeAwfTask.php
│ ├── CurlSftpTask.php
│ ├── DocBookToEpubTask.php
│ ├── GitDateTask.php
│ ├── GitHubAssetTask.php
│ ├── GitHubReleaseTask.php
│ ├── GitVersionTask.php
│ ├── JPATask.php
│ ├── LinkTask.php
│ ├── PhpStormSources.php
│ ├── ProjectLinkTask.php
│ ├── RelinkSiteTask.php
│ ├── RelinkWPSiteTask.php
│ ├── WordPressEntryPointTask.php
│ ├── XmlUpdateTask.php
│ ├── XmlVersionTask.php
│ ├── ZipmeTask.php
│ └── library
│ ├── GitHubTask.php
│ ├── JPAFileSet.php
│ ├── ZipmeFileSet.php
│ └── jpa.php
└── src
├── Composer
└── InstallationScript.php
└── LinkLib
├── LinkHelper.php
├── MapResult.php
├── ProjectLinker.php
├── Relink.php
├── ScanResult.php
├── Scanner
├── AbstractScanner.php
├── Component.php
├── File.php
├── Library.php
├── Module.php
├── Package.php
├── Plugin.php
└── Template.php
└── ScannerInterface.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text eol=lf
3 |
4 | # Explicitly declare text files you want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.php text eol=lf
7 | *.phps text eol=lf
8 | *.inc text eol=lf
9 | *.js text eol=lf
10 | *.css text eol=lf
11 | *.ini text eol=lf
12 | *.json text eol=lf
13 | *.htm text eol=lf
14 | *.html text eol=lf
15 | *.xml text eol=lf
16 | *.xslt text eol=lf
17 | *.svg text eol=lf
18 | *.txt text eol=lf
19 | *.md text eol=lf
20 | *.sh text eol=lf
21 | CHANGELOG text eol=lf
22 | README text eol=lf
23 | RELEASENOTES text eol=lf
24 |
25 | # Declare files that will always have CRLF line endings on checkout.
26 | *.sln text eol=crlf
27 |
28 | # Denote all files that are truly binary and should not be modified.
29 | *.acorn binary
30 | *.png binary
31 | *.jpg binary
32 | *.jpeg binary
33 | *.z binary
34 | *.gif binary
35 | *.jpa binary
36 | *.jps binary
37 | *.zip binary
38 | *.dll binary
39 | *.exe binary
40 | *.jar binary
41 | *.phar binary
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # PhpStorm
2 | /.idea
3 |
4 | # Cache
5 | /cache
6 |
7 | # Composer
8 | /vendor
9 |
10 | # DocBook XSL Stylesheets
11 | phing/bin/dbxsl
12 |
13 | # NPM
14 | /node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Akeeba Build Tools
2 |
3 | Akeeba Build Tools is an extension builder for Joomla!. It contains the common build files of the Akeeba extensions, but can also be used for building your own Joomla! extensions. Akeeba Build Tools is NOT a standalone builder, it needs an extension specific Phing build file.
4 |
5 | Feel free to fork and contribute.
6 |
7 | ## Quick documentation links
8 |
9 | Learn more about the [Common Phing Script](phing/README.md).
10 |
11 | ## COPYRIGHT AND DISCLAIMER
12 | Akeeba Build Tools - Extension builder for Joomla!
13 | Copyright (c)2010-2025 Nicholas K. Dionysopoulos / Akeeba Ltd
14 |
15 | This program is free software: you can redistribute it and/or modify
16 | it under the terms of the GNU General Public License as published by
17 | the Free Software Foundation, either version 3 of the License, or
18 | (at your option) any later version.
19 |
20 | This program is distributed in the hope that it will be useful,
21 | but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | GNU General Public License for more details.
24 |
25 | You should have received a copy of the GNU General Public License
26 | along with this program. If not, see .
27 |
--------------------------------------------------------------------------------
/clitools/all/README.md:
--------------------------------------------------------------------------------
1 | # The `all` tool
2 |
3 | This script can be used under any bash shell such as the one commonly used in Linux and Mac OS X, or the bash shell provided in Windows by Git Bash, Cygwin etc. It is meant to iterate through each of the subdirectories, figure out if it contains a Git repository and then pull or push it.
4 |
5 | Usage: ./all pull|push
6 |
7 | Pulling all repositories:
8 |
9 | `$ ./all pull`
10 |
11 | This runs a `git pull --all` on all first level subdirectories containing a Git working copy
12 |
13 | Pushing all repositories:
14 |
15 | `$ ./all push`
16 |
17 | This runs a `git push; git push --tags` on all first level subdirectories containing a Git working copy
--------------------------------------------------------------------------------
/clitools/all/all:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # @package buildfiles
4 | # @copyright Copyright (c)2010-2025 Nicholas K. Dionysopoulos / Akeeba Ltd
5 | # @license GNU General Public License version 3, or later
6 | #
7 |
8 | usage() {
9 | echo -e $COLOR_BROWN"Usage: %0 "$COLOR_NC
10 | echo ""
11 | echo -e $COLOR_BLUE"All repositories"$COLOR_NC
12 | echo "pull Pull from Git"
13 | echo "push Push to Git"
14 | echo "tidy Perform local Git repo housekeeping"
15 | echo "status Report repositories with uncommitted changes"
16 | echo "branch Which branch am I in?"
17 | echo "cloneme Generate git clone commands"
18 | echo "version Latest version versus latest tag information"
19 | echo -e $COLOR_BLUE"Using Akeeba Build Files"$COLOR_NC
20 | echo "build Run the Phing 'git' task to rebuild the software"
21 | echo "link Internal relink"
22 | echo "relink Relink to a site, e.g. %0 relink /var/www/mysite"
23 | echo "update Push updates to the CDN (uses Akeeba Release Maker)"
24 |
25 | exit 255
26 | }
27 |
28 | COLOR_NC='\033[0m'
29 | COLOR_WHITE='\033[1;37m'
30 | COLOR_BLACK='\033[0;30m'
31 | COLOR_BLUE='\033[0;34m'
32 | COLOR_LIGHT_BLUE='\033[1;34m'
33 | COLOR_GREEN='\033[0;32m'
34 | COLOR_LIGHT_GREEN='\033[1;32m'
35 | COLOR_CYAN='\033[0;36m'
36 | COLOR_LIGHT_CYAN='\033[1;36m'
37 | COLOR_RED='\033[0;31m'
38 | COLOR_LIGHT_RED='\033[1;31m'
39 | COLOR_PURPLE='\033[0;35m'
40 | COLOR_LIGHT_PURPLE='\033[1;35m'
41 | COLOR_BROWN='\033[0;33m'
42 | COLOR_YELLOW='\033[1;33m'
43 | COLOR_GRAY='\033[0;30m'
44 | COLOR_LIGHT_GRAY='\033[0;37m'
45 |
46 | if [ $# -lt 1 ]; then
47 | usage
48 |
49 | exit 255
50 | fi
51 |
52 | echo -e $COLOR_WHITE"All – Loop all Git repositories"$COLOR_NC
53 | echo ""
54 |
55 | for d in *; do
56 | # Skip over files
57 | if [[ ! -d "$d" ]]; then
58 | continue
59 | fi
60 |
61 | # Skip over symlinks
62 | if [[ -L "$d" ]]; then
63 | continue
64 | fi
65 |
66 | # Make sure it's a Git repo
67 | if [ ! -d "$d/.git" ]; then
68 | continue
69 | fi
70 |
71 | # Go into the directory
72 | pushd $d >/dev/null
73 |
74 | # THISREPO_LINES=$(git remote -v | grep git@github.com | wc -l | awk '{print $1}')
75 | #
76 | # if [ $THISREPO_LINES -lt 1 ]; then
77 | # popd >/dev/null
78 | #
79 | # continue
80 | # fi
81 |
82 | case "$1" in
83 | pull)
84 | echo -e "\n"$COLOR_LIGHT_BLUE"Pulling $COLOR_CYAN$d"$COLOR_NC
85 | git pull --all -p -t --jobs=4
86 | ;;
87 |
88 | push)
89 | echo -e "\n"$COLOR_LIGHT_GREEN"Pushing $COLOR_CYAN$d"$COLOR_NC
90 | git push
91 | git push --tags
92 | ;;
93 |
94 | tidy)
95 | echo -e "\n"$COLOR_RED"Housekeeping $COLOR_CYAN$d"$COLOR_NC
96 | git remote prune origin
97 | git gc
98 | ;;
99 |
100 | cloneme)
101 | #ZZZ_CUR_BRANCH=$(git rev-parse --abbrev-ref HEAD)
102 | ZZZ_CUR_BRANCH=$(git branch --show-current --abbrev)
103 | ZZZ_REMOTE=$(git config --get remote.origin.url)
104 | ZZZ_TAG=$(git describe --exact-match --tags 2>/dev/null)
105 |
106 | echo git clone --single-branch -b $ZZZ_CUR_BRANCH "$ZZZ_REMOTE" $d
107 |
108 | if [[ ! -z "$ZZZ_TAG" ]]
109 | then
110 | echo git -C $d switch -c $ZZZ_CUR_BRANCH
111 | fi
112 | ;;
113 |
114 | branch)
115 | ZZZ_CUR_BRANCH=$(git rev-parse --abbrev-ref HEAD)
116 | ZZZ_COLOR=$COLOR_LIGHT_RED
117 |
118 | if [[ "$ZZZ_CUR_BRANCH" == "development" ]]; then
119 | ZZZ_COLOR=$COLOR_LIGHT_GREEN
120 | elif [[ "$ZZZ_CUR_BRANCH" == "master" ]]; then
121 | ZZZ_COLOR=$COLOR_YELLOW
122 | elif [[ "$ZZZ_CUR_BRANCH" == "main" ]]; then
123 | ZZZ_COLOR=$COLOR_YELLOW
124 | elif [[ "$ZZZ_CUR_BRANCH" == "kyrion" ]]; then
125 | ZZZ_COLOR=$COLOR_YELLOW
126 | fi
127 |
128 | echo -ne $COLOR_LIGHT_PURPLE"Branch "$COLOR_CYAN
129 | printf '%-25s' $d
130 | echo -e $ZZZ_COLOR$ZZZ_CUR_BRANCH$COLOR_NC
131 | ;;
132 |
133 | status)
134 | #i=`expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)`
135 | i=$(expr $(git status --porcelain 2>/dev/null | wc -l))
136 | if [ $i -ne 0 ]; then
137 | echo -e $COLOR_LIGHT_RED"Dirty $COLOR_CYAN$d"$COLOR_NC
138 | fi
139 | ;;
140 |
141 | link)
142 | if [ -d build ]; then
143 | echo -e "\n"$COLOR_BROWN"Linking $COLOR_CYAN$d"$COLOR_NC
144 | cd build
145 | phing link
146 | fi
147 | ;;
148 |
149 | build)
150 | if [ -f build.xml ]; then
151 | echo -e "\n"$COLOR_BROWN"Building $COLOR_CYAN$d"$COLOR_NC
152 | phing git
153 | fi
154 |
155 | if [ -d build ]; then
156 | echo -e "\n"$COLOR_BROWN"Building $COLOR_CYAN$d"$COLOR_NC
157 | cd build
158 | phing git
159 | fi
160 | ;;
161 |
162 | relink)
163 | if [ -f build.xml ]; then
164 | echo -e "\n"$COLOR_BROWN"Relinking $COLOR_CYAN$d"$COLOR_NC
165 | phing relink -Dsite=$2
166 | fi
167 |
168 | if [ -d build ]; then
169 | echo -e "\n"$COLOR_BROWN"Relinking $COLOR_CYAN$d"$COLOR_NC
170 | cd build
171 | phing relink -Dsite=$2
172 | fi
173 | ;;
174 |
175 | update)
176 | if [ -f build.xml ]; then
177 | echo -e "\n"$COLOR_BROWN"Pushing updates for $COLOR_CYAN$d"$COLOR_NC
178 | phing update
179 | fi
180 |
181 | if [ -d build ]; then
182 | echo -e "\n"$COLOR_BROWN"Pushing updates for $COLOR_CYAN$d"$COLOR_NC
183 | cd build
184 | phing update
185 | fi
186 | ;;
187 |
188 | version)
189 | if [[ (! -f CHANGELOG.md) && (! -f CHANGELOG) ]]; then
190 | popd >/dev/null
191 | continue
192 | fi
193 | if [ -f CHANGELOG ]; then
194 | CHANGELOG_FILE=CHANGELOG
195 | else
196 | CHANGELOG_FILE=CHANGELOG.md
197 | fi
198 | LATEST_VERSION=$(grep -o "[[:blank:]]\([0-9]\{1,\}\.\)\{2\}\d\{1,\}" $CHANGELOG_FILE | head -n 1)
199 | LATEST_VERSION=$(echo $LATEST_VERSION | sed -e 's/^[[:space:]]*//')
200 | LATEST_TAG=$(git describe --abbrev=0 --always)
201 |
202 | if [[ "$LATEST_VERSION" != "$LATEST_TAG" ]]
203 | then
204 | echo -en "${COLOR_LIGHT_RED}Unreleased $COLOR_CYAN$d$COLOR_NC "
205 | echo -e "${COLOR_LIGHT_RED}$LATEST_VERSION$COLOR_NC ($COLOR_BROWN$LATEST_TAG$COLOR_NC)"
206 | else
207 | echo -en "${COLOR_LIGHT_GREEN}Released $COLOR_CYAN$d$COLOR_NC "
208 | echo -e "$COLOR_BROWN$LATEST_TAG$COLOR_NC"
209 | fi
210 | ;;
211 |
212 | *)
213 | echo Unknown command "$1"
214 |
215 | popd >/dev/null
216 |
217 | exit
218 | ;;
219 | esac
220 |
221 | popd >/dev/null
222 | done
223 |
--------------------------------------------------------------------------------
/clitools/all/all.ps1:
--------------------------------------------------------------------------------
1 | ## @package buildfiles
2 | ## @copyright Copyright (c)2010-2025 Nicholas K. Dionysopoulos / Akeeba Ltd
3 | ## @license GNU General Public License version 3, or later
4 |
5 | Param(
6 | [string]$operation,
7 | [string]$sitepath
8 | )
9 |
10 | function showUsage()
11 | {
12 | Write-Host "Usage: all " -Foreground DarkYellow
13 | Write-Host ""
14 | Write-Host All repositories -Foreground Blue
15 | Write-Host "pull Pull from Git"
16 | Write-Host "push Push to Git"
17 | Write-Host "tidy Perform local Git repo housekeeping"
18 | Write-Host "status Report repositories with uncommitted changes"
19 | Write-Host "branch Which Git branch am I in?"
20 | Write-Host "fixcrlf Fix CRLF under Windows"
21 | Write-Host Using Akeeba Build Files -Foreground Blue
22 | Write-Host "build Run the Phing 'git' task to rebuild the software"
23 | Write-Host "link Internal relink"
24 | Write-Host "relink Relink to a site, e.g. all relink c:\sites\mysite"
25 | }
26 |
27 | if (!$operation)
28 | {
29 | showUsage
30 | exit 255
31 | }
32 |
33 | Write-Host "All - Loop all repositories" -Foreground White
34 | Write-Host ""
35 |
36 | Get-ChildItem -Directory | ForEach-Object {
37 | $d = $_.Name
38 | Push-Location $d
39 |
40 | $hasDotGit = Test-Path .git
41 |
42 | if ($hasDotGit -eq $False)
43 | {
44 | Pop-Location
45 |
46 | return
47 | }
48 |
49 | $thisRepoMetrics = git remote -v | Select-String -Pattern "git@github.com" | Measure-Object -Line
50 |
51 | if ($thisRepoMetrics.Lines -lt 1)
52 | {
53 | Pop-Location
54 |
55 | return
56 | }
57 |
58 | Switch ($operation)
59 | {
60 | "pull" {
61 | Write-Host "Pulling " -Foreground Blue -NoNewline
62 | Write-Host $d -Foreground Cyan
63 | git pull --all
64 | }
65 |
66 | "push" {
67 | Write-Host "Pushing " -Foreground Green -NoNewline
68 | Write-Host $d -Foreground Cyan
69 | git push --all
70 | }
71 |
72 | "tidy" {
73 | Write-Host "Housekeeping " -Foreground Red -NoNewline
74 | Write-Host $d -Foreground Cyan
75 | git remote prune origin
76 | git gc
77 | }
78 |
79 | "status" {
80 | if ( (git status --porcelain | Measure-Object -Line).Lines -gt 0)
81 | {
82 | Write-Host "Dirty " -Foreground Red -NoNewline
83 | Write-Host $d -Foreground Cyan
84 | }
85 | }
86 |
87 | "link" {
88 | if (Test-Path build.xml)
89 | {
90 | Write-Host "Linking " -Foreground Red -NoNewline
91 | Write-Host $d -Foreground Cyan
92 |
93 | phing link
94 | }
95 |
96 | if (Test-Path build)
97 | {
98 | Write-Host "Linking " -Foreground Red -NoNewline
99 | Write-Host $d -Foreground Cyan
100 |
101 | cd build
102 | phing link
103 | }
104 | }
105 |
106 | "build" {
107 | if (Test-Path build.xml)
108 | {
109 | Write-Host "Building " -Foreground Red -NoNewline
110 | Write-Host $d -Foreground Cyan
111 |
112 | phing git
113 | }
114 |
115 | if (Test-Path build)
116 | {
117 | Write-Host "Building " -Foreground Red -NoNewline
118 | Write-Host $d -Foreground Cyan
119 |
120 | cd build
121 | phing git
122 | }
123 | }
124 |
125 | "relink" {
126 | if (Test-Path build.xml)
127 | {
128 | Write-Host "Relinking " -Foreground Red -NoNewline
129 | Write-Host $d -Foreground Cyan
130 |
131 | phing relink -Dsite="${sitepath}"
132 | }
133 |
134 | if (Test-Path build)
135 | {
136 | Write-Host "Relinking " -Foreground Red -NoNewline
137 | Write-Host $d -Foreground Cyan
138 |
139 | cd build
140 | phing relink -Dsite="${sitepath}"
141 | }
142 | }
143 |
144 | "branch" {
145 | $currentBranch = git rev-parse --abbrev-ref HEAD
146 | $color = "Red"
147 |
148 | if ($currentBranch -eq "development") {
149 | $color = "Green"
150 | } elseif ($currentBranch -eq "master") {
151 | $color = "Yellow"
152 | } elseif ($currentBranch -eq "main") {
153 | $color = "Yellow"
154 | } elseif ($currentBranch -eq "kyrion") {
155 | $color = "Yellow"
156 | }
157 |
158 | Write-Host "Branch " -Foreground Magenta -NoNewline
159 | "{0, -25}" -f $d | Write-Host -Foreground Cyan -NoNewline
160 | Write-Host `t$currentBranch -Foreground $color
161 | }
162 |
163 | "fixcrlf" {
164 | Write-Host "Fixing CRLF " -Foreground DarkGreen -NoNewline
165 | Write-Host $d -Foreground Cyan
166 |
167 | git config --unset core.fileMode
168 | git config --unset core.filemode
169 | git config --unset core.autocrlf
170 | }
171 |
172 | "default" {
173 | Write-Host "Unknown command $operation" -Foreground Magenta
174 |
175 | Pop-Location
176 |
177 | Exit 1
178 | }
179 | }
180 |
181 | Pop-Location
182 | }
183 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "akeeba/buildtools",
3 | "description": "Build utilities for Akeeba software",
4 | "license": "GPL-3.0-or-later",
5 | "minimum-stability": "beta",
6 | "require": {
7 | "php": "^8.1",
8 | "ext-zip": "*",
9 | "akeeba/s3": "dev-development",
10 | "composer/ca-bundle": "^1.2",
11 | "corneltek/getoptionkit": "^2.6.0",
12 | "czproject/git-php": "^4.0.3",
13 | "knplabs/github-api": "^3.0",
14 | "nyholm/psr7": "^1.0",
15 | "php-http/guzzle6-adapter": "^2.0",
16 | "rector/rector": "^0.10"
17 | },
18 | "suggest": {
19 | "ext-curl": "*",
20 | "ext-dom": "*",
21 | "ext-fileinfo": "*",
22 | "ext-libxml": "*",
23 | "ext-simplexml": "*",
24 | "ext-ssh2": "*",
25 | "ext-xsl": "*",
26 | "ext-zlib": "*"
27 | },
28 | "autoload": {
29 | "psr-4" : {
30 | "Akeeba\\BuildFiles\\": "src"
31 | }
32 | },
33 | "config": {
34 | "platform" : {
35 | "php": "8.1.999"
36 | },
37 | "allow-plugins": {
38 | "rector/extension-installer": true,
39 | "php-http/discovery": true
40 | }
41 | },
42 | "extra" : {
43 | "dbxsl" : {
44 | "source": "https://github.com/docbook/xslt10-stylesheets/releases/download/release%2F1.79.2/docbook-xsl-1.79.2.zip"
45 | }
46 | },
47 | "scripts": {
48 | "npm-deps": [
49 | "npm install"
50 | ],
51 | "post-install-cmd": [
52 | "\\Akeeba\\BuildFiles\\Composer\\InstallationScript::installDbXsl",
53 | "@npm-deps"
54 | ],
55 | "post-update-cmd": [
56 | "\\Akeeba\\BuildFiles\\Composer\\InstallationScript::installDbXsl",
57 | "npm update",
58 | "@npm-deps"
59 | ],
60 | "pre-archive-cmd": [
61 | "@npm-deps"
62 | ],
63 | "post-create-project-cmd": [
64 | "\\Akeeba\\BuildFiles\\Composer\\InstallationScript::installDbXsl",
65 | "@composer dump-autoload -a"
66 | ]
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/linklib/include.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
16 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/10.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/10.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
19 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/11.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/11.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
17 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/12.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/12.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
19 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/13.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/13.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
21 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/14.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/14.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
18 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/15.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/15.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
20 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/16.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
21 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/17.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
18 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/18.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
22 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/19.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
21 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/2.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
18 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/20.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
21 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/21.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
19 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/22.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
21 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/23.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
23 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/24.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
20 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/25.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
22 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/26.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
23 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/27.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
20 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/28.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
24 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/29.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
23 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/3.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
20 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/30.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
23 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/4.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
17 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/5.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/5.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
19 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/6.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/6.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
20 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/7.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/7.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
17 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/8.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/8.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
21 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/9.png
--------------------------------------------------------------------------------
/phing/bin/images/callouts/9.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | ]>
7 |
20 |
--------------------------------------------------------------------------------
/phing/bin/images/callouts/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/callouts/Thumbs.db
--------------------------------------------------------------------------------
/phing/bin/images/draft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/draft.png
--------------------------------------------------------------------------------
/phing/bin/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/home.png
--------------------------------------------------------------------------------
/phing/bin/images/important.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/important.png
--------------------------------------------------------------------------------
/phing/bin/images/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/next.png
--------------------------------------------------------------------------------
/phing/bin/images/note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/note.png
--------------------------------------------------------------------------------
/phing/bin/images/prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/prev.png
--------------------------------------------------------------------------------
/phing/bin/images/tip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/tip.png
--------------------------------------------------------------------------------
/phing/bin/images/toc-blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/toc-blank.png
--------------------------------------------------------------------------------
/phing/bin/images/toc-minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/toc-minus.png
--------------------------------------------------------------------------------
/phing/bin/images/toc-plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/toc-plus.png
--------------------------------------------------------------------------------
/phing/bin/images/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/up.png
--------------------------------------------------------------------------------
/phing/bin/images/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeeba/buildfiles/4ac4a1e56bd78bf5b144312cf90dcfb63bc08cd3/phing/bin/images/warning.png
--------------------------------------------------------------------------------
/phing/default.properties:
--------------------------------------------------------------------------------
1 | ;; =============================================================================
2 | ;; Default values for properties
3 | ;; =============================================================================
4 | ;;
5 | ;; You are supposed to override these properties in one of the following files:
6 | ;;
7 | ;; /../build.properties Common across all of your projects (passwords, not committed to Git)
8 | ;; /build/build.properties Public project properties (no passwords, committed to Git)
9 | ;; /build/override.properties Temporary overrides for testing (passwords, not committed to Git)
10 |
11 | ; ------------------------------------------------------------------------------
12 | ; Environment
13 | ; ------------------------------------------------------------------------------
14 | ; Path to the PHP-CLI executable
15 | system.php_cli=php
16 | ; Path to the Composer executable
17 | bin.composer=composer
18 | ; Either --dev or --no-dev
19 | composer.dev_argument=--no-dev
20 | ; Set to 1 to use built-in PHP XSLTproc when generating documentation. Set to 0 to use the docs.xsltproc binary (default).
21 | docs.php_xsltproc=1
22 | ; Path to the xsltproc binary
23 | docs.xsltproc=xsltproc
24 |
25 | ; ------------------------------------------------------------------------------
26 | ; Build stuff
27 | ; ------------------------------------------------------------------------------
28 | ; Path to the Joomla! site to symlink the extensions to
29 | build.relink_site=
30 | ; Path to the WordPress site to symlink the plugin to
31 | build.relink_sitewp=
32 | ; Should I allow the Joomla! package extension building to fail? Set to 1 if you are building a single component / module / plugin instead of a package containing multiple extensions.
33 | build.allow_joomla_package_fail=0
34 | ; Version of the software being built
35 | version=git
36 |
37 | ; ------------------------------------------------------------------------------
38 | ; Joomla! package building
39 | ; ------------------------------------------------------------------------------
40 | ; The name of the component, must be in the form something, NOT com_something!
41 | build.component=example
42 | ; Do you have a Core / Pro version? If this is 0 only the Core release will be built
43 | build.has_pro=1
44 |
45 | ; ------------------------------------------------------------------------------
46 | ; Amazon S3 configuration
47 | ; ------------------------------------------------------------------------------
48 | ; Amazon S3 access key. Generate an access/private pair through IAM.
49 | s3.access=AXAXAXAXAXAXAXAXAXAX
50 | ; Amazon S3 private key
51 | s3.private=abcdEfgh/iJklmnOpqrStuvWxYZ01234567890x0
52 | ; Amazon S3 bucket for public release files and updates
53 | s3.bucket=mybucket
54 | ; Language files path relative to bucket's root
55 | s3.path=language
56 | ; Amazon S3 directory where files are uploaded
57 | s3.directory=downloads/phingtest
58 | ; CloudFront CDN for the Amazon S3 bucket
59 | s3.cdnhostname=cdn.example.com
60 | ; Use HTTPS for the Amazon S3 REST API?
61 | s3.tls = true
62 | ; Amazon S3 ACL for uploaded files
63 | s3.acl=public-read
64 | ; Amazon S3 storage class for uploaded files
65 | s3.storage_class=STANDARD
66 | ; Maximum cache age for uploaded files (in seconds)
67 | s3.maximum_age=600
68 |
69 | ; ------------------------------------------------------------------------------
70 | ; SFTP Setup
71 | ; ------------------------------------------------------------------------------
72 | ; SFTP connection information
73 | ;; Hostname, e.g. sftp.example.com
74 | scp.host=
75 | ;; TCP/IP Port
76 | scp.port=22
77 | ;; Your SFTP username, e.g. myhostinguser
78 | scp.username=
79 | ;; OPTION 1: PASSWORD AUTHENTICATION. Your SFTP password.
80 | scp.password=
81 | ;; OPTION 2: CERTIFICATE AUTHENTICATION. The path to your public key file, e.g. /home/user/.ssh/id_rsa.pub
82 | scp.pubkeyfile=
83 | ;; OPTION 2: CERTIFICATE AUTHENTICATION. The path to your private key file, e.g. /home/user/.ssh/id_rsa
84 | scp.privkeyfile=
85 | ;; OPTION 2: CERTIFICATE AUTHENTICATION. The password to your private key file. Leave empty if nto password protected.
86 | scp.privkeyfilepassphrase=
87 | ; SFTP directory for the ARS repository root
88 | scp.dir=/var/www/html
89 | ; SFTP directory for the DocImport public media folder
90 | scp.dir.docs=/var/www/html/media/com_docimport
91 |
92 | ; ------------------------------------------------------------------------------
93 | ; SFTP deploy for dev releases
94 | ; ------------------------------------------------------------------------------
95 | ; SFTP Deploy patterns. Files matching these patterns will be uploaded when doing `phing ftpdeploy`
96 | ftpdeploy.pattern.core=com_example-*-core.zip
97 | ftpdeploy.pattern.pro=com_example-*-pro.zip
98 |
99 | ; SFTP Deploy paths. These are relative to scp.dir above.
100 | ftpdeploy.path.core=files/dev/examplecore
101 | ftpdeploy.path.pro=files/dev/examplepro
102 |
103 | ; ------------------------------------------------------------------------------
104 | ; Akeeba Release Maker workflow setup
105 | ; ------------------------------------------------------------------------------
106 | ; Release method: json (Release Maker 1.x/2.x, legacy), yaml (Release Maker 2.x) or github
107 | release.method=json
108 |
109 | ; URL to the site hosting Akeeba Release System. IT'D BETTER BE HTTPS FOR SECURITY REASONS!
110 | release.api.endpoint=https://www.example.com
111 | ; API connector (php or curl)
112 | release.api.connector=php
113 | ; Super User username and password (DEPRECATED; DEPENDS ON A FEATURE DISABLED BY DEFAULT IN JOOMLA!)
114 | release.api.username=admin
115 | release.api.password=notsosecret
116 | ; Super User Joomla! Token
117 | release.api.token=somelongtokenstringhere
118 |
119 | ; Akeeba Release Maker steps
120 | release.steps = "prepare","deploy","release","items","publish","updates"
121 | release.steps_update = "updates"
122 |
123 | ; ARS category for these downloads
124 | release.category=1
125 | ; How should I deploy updates? sftp, s3
126 | release.updatemethod=s3
127 | ; Joomla access level for the release
128 | release.access=1
129 | ; Path for update XML and INI streams relative to bucket's root or the FTP/SFTP root folder
130 | release.update_dir=updates
131 |
132 | ; --- CORE ----
133 | ; How should I release the Core version? ftp, sftp, s3
134 | release.core.method=s3
135 | ; Relative path. It's relative either to s3.directory (s3) or scp.dir (sftp)
136 | release.core.dir=downloads/akeebabackup
137 | ; Pattern for package files
138 | release.core.pattern=com_*core.zip
139 | ; Update basename (without .xml extension)
140 | release.core.update_basename=examplecore
141 | ; Update stream ID
142 | release.core.update_stream=1
143 | ; Viewing Access Level for these files
144 | release.core.access_level=1
145 | ; Update information formats to generate, Core version
146 | release.core.update.formats="ini", "inibare", "xml"
147 |
148 | ; ---- PRO ----
149 | ; How should I release the Pro version? ftp, sftp, s3
150 | release.pro.method=sftp
151 | ; Relative path
152 | release.pro.dir=files/normal/examplepro
153 | ; Pattern for package files
154 | release.pro.pattern=com_*pro.zip
155 | ; Update basename (without .xml extension)
156 | release.pro.update.basename=examplepro
157 | ; Update stream ID
158 | release.pro.update_stream=2
159 | ; Viewing Access Level for these files
160 | release.pro.access_level=123
161 | ; Update information formats to generate, Pro version
162 | release.pro.update.formats="ini", "inibare", "xml"
163 |
164 | ; ---- DOCUMENTATION ----
165 | ; Where should I upload the documentation? [core|pro]
166 | release.docs.where=core
167 | ; Which files should I upload? Provide a list in JSON array format
168 | release.docs.which=["my-docs", "other-docs", "whatever"]
169 |
170 | ; ------------------------------------------------------------------------------
171 | ; GitHub Releases setup
172 | ; ------------------------------------------------------------------------------
173 | ; The Git binary. Not necessary in common.xml but it's worth setting in my OS-specific build.properties file.
174 | git.binary=/usr/bin/git
175 | ; The GitHub organization or user the repository is under
176 | github.organization=foobar
177 | ; The GitHub repository name
178 | github.repository=baz
179 | ; GitHub personal access token from https://github.com/settings/tokens
180 | github.token=yourGitHubTokenHere
181 |
--------------------------------------------------------------------------------
/phing/epub/com.apple.ibooks.display-options.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/phing/epub/docbook-epub.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | /********************************/
4 | /* start of styles in block.xsl */
5 |
6 | .formalpara-title {
7 | font-weight: bold;
8 | }
9 |
10 | div.blockquote-title {
11 | font-weight: bold;
12 | margin-top: 1em;
13 | margin-bottom: 1em;
14 | }
15 |
16 | span.msgmain-title {
17 | font-weight: bold;
18 | }
19 |
20 | span.msgsub-title {
21 | font-weight: bold;
22 | }
23 |
24 | span.msgrel-title {
25 | font-weight: bold;
26 | }
27 |
28 | div.msglevel, div.msgorig, div.msgaud {
29 | margin-top: 1em;
30 | margin-bottom: 1em;
31 | }
32 |
33 | span.msglevel-title, span.msgorig-title, span.msgaud-title {
34 | font-weight: bold;
35 | }
36 |
37 | div.msgexplan {
38 | margin-top: 1em;
39 | margin-bottom: 1em;
40 | }
41 |
42 | span.msgexplan-title {
43 | font-weight: bold;
44 | }
45 |
46 | div.sidebar {
47 | border: 0.5pt solid black;
48 | margin-top: 1em;
49 | margin-bottom: 1em;
50 | padding: 8px;
51 | }
52 |
53 | /* end of styles in block.xsl */
54 | /********************************/
55 |
56 | /********************************/
57 | /* start of styles in autotoc.xsl */
58 |
59 | /* top level entries */
60 | nav>ol>li>a {
61 | margin-top: 1em;
62 | margin-bottom: 1em;
63 | }
64 |
65 |
66 | /* end of styles in autotoc.xsl */
67 | /********************************/
68 |
69 | /********************************/
70 | /* start of styles in formal.xsl */
71 |
72 | div.figure-title {
73 | font-weight: bold;
74 | }
75 |
76 | div.example-title {
77 | font-weight: bold;
78 | }
79 |
80 | div.equation-title {
81 | font-weight: bold;
82 | }
83 |
84 | div.table-title {
85 | font-weight: bold;
86 | }
87 |
88 | div.sidebar-title {
89 | font-weight: bold;
90 | }
91 |
92 |
93 | /* end of styles in formal.xsl */
94 | /********************************/
95 |
96 | /********************************/
97 | /* start of styles in verbatim.xsl */
98 |
99 | div.programlisting {
100 | white-space: pre-wrap;
101 | font-family: monospace;
102 | }
103 |
104 | div.screen {
105 | white-space: pre-wrap;
106 | font-family: monospace;
107 | }
108 |
109 | div.synopsis {
110 | white-space: pre-wrap;
111 | font-family: monospace;
112 | }
113 |
114 | /* end of styles in verbatim.xsl */
115 | /********************************/
116 |
117 | /********************************/
118 | /* NAVIGATION */
119 |
120 | nav ol {
121 | list-style-type: none;
122 | margin: 0 0 0 2em;
123 | padding: 0 0 0 0;
124 | }
125 |
126 | nav ol li {
127 | margin: 0 0 0 0;
128 | padding: 0 0 0 0;
129 | }
130 |
131 | nav ol li a {
132 | text-decoration: none;
133 | color: black;
134 | font-family: sans-serif;
135 | }
136 |
137 | #guide {
138 | display: none;
139 | }
140 |
141 |
142 | /** Akeeba Ltd Customisations **/
143 | /* Base style */
144 | html {
145 | font-size: 11pt;
146 | text-align: justify;
147 | font-family: "Avenir-Book", "HelveticaNeue", "Helvetica", "Arial", sans;
148 | }
149 |
150 | /* Structure */
151 |
152 | dl { margin-left: 2em; }
153 | dd { margin-left: 1em; }
154 |
155 | h1.title {
156 | font-weight: bold;
157 | text-align: center;
158 | }
159 |
160 | h2.title {
161 | font-weight: bold;
162 | border-bottom: thin solid #999;
163 | }
164 |
165 | h3.title {
166 | font-weight: bold;
167 | border-bottom: thin solid #ccc;
168 | }
169 |
170 | table tr,
171 | table td {
172 | border: none;
173 | }
174 |
175 | table p {
176 | margin-bottom: 6pt;
177 | }
178 |
179 | pre {
180 | margin-bottom: 6pt;
181 | }
182 |
183 | /* Admonitions */
184 |
185 | .warning,
186 | .important,
187 | .note,
188 | .tip {
189 | text-align: justify;
190 | border-left: 5px solid black;
191 | margin: 0.5em 1em !important;
192 | padding: 0.3em 0.5em !important;
193 | border-radius: 1em;
194 | }
195 |
196 | .warning h3.title,
197 | .important h3.title,
198 | .note h3.title,
199 | .tip h3.title {
200 | padding: 0;
201 | margin: 0;
202 | font-size: medium;
203 | font-weight: normal;
204 | font-style: italic;
205 | border: none;
206 | }
207 |
208 | .warning img,
209 | .important img,
210 | .note img,
211 | .tip img {
212 | padding: 0.2em 0.5em 0 0;
213 | }
214 |
215 | .warning p,
216 | .important p,
217 | .note p,
218 | .tip p {
219 | margin: 0.3em 0;
220 | }
221 |
222 | .warning { background-color: #ffe0e0; border-color: #990000; }
223 | .warning h3 {color: #660000;}
224 | .important { background-color: #ffffe0; border-color: #999900; }
225 | .important h3 {color: #666600;}
226 | .note { background-color: #e0ffe0; border-color: #009900; }
227 | .note h3 {color: #006600;}
228 | .tip { background-color: #eeeeff; border-color: #000099; }
229 | .tip h3 {color: #000066;}
230 |
231 | .warning th { color: #660000; font-size: larger; }
232 | .important th { color: #666600; font-size: larger; }
233 | .note th { color: #006600; font-size: larger; }
234 | .tip th { color: #000066; font-size: larger; }
235 |
236 | /* Fixed width */
237 |
238 | pre.programlisting {
239 | border: 1px solid #333333;
240 | background-color: #f0f0f0;
241 | font-family: "Consolas", "Courier New", "Liberation Mono", monospace;
242 | color: #003300;
243 | border-radius: 5px;
244 | padding: 3px;
245 | }
246 |
247 | /* Document elements */
248 | .screenshot-title {
249 | font-size: x-small;
250 | color: #666;
251 | }
252 |
253 | .replaceable {
254 | font-style: italic;
255 | text-decoration: underline;
256 | }
257 |
258 | code {
259 | font-family: "Consolas", "Courier New", "Liberation Mono", monospace;
260 | }
261 |
262 | code.filename {
263 | color: #363;
264 | }
265 |
266 | .guimenu,
267 | .guisubmenu,
268 | .guilabel {
269 | font-weight: bold;
270 | }
271 |
272 | .guilabel {
273 | color: #333399;
274 | }
275 |
276 | .guibutton {
277 | color: black;
278 | border: 1px solid #aaaaaa;
279 | border-radius: 0.2em;
280 | padding: 0 0.25em;
281 | background: #f1f1f1;
282 | box-shadow: -0.1em -0.1em 0.2em 0.1em #ccc inset;
283 | }
284 |
285 | dt .term {
286 | font-weight: bold;
287 | color: #663333;
288 | }
289 |
290 | trademark {
291 | color: #336633;
292 | }
293 |
--------------------------------------------------------------------------------
/phing/epub/mimetype:
--------------------------------------------------------------------------------
1 | application/epub+zip
--------------------------------------------------------------------------------
/phing/release.json:
--------------------------------------------------------------------------------
1 | {
2 | "common.version": "##VERSION##",
3 | "common.date": "##DATE##",
4 | "common.arsapiurl": "##API.ENDPOINT##",
5 | "common.username": "##API.USERNAME##",
6 | "common.password": "##API.PASSWORD##",
7 | "common.token": "##API.TOKEN##",
8 | "common.category": "##RELEASECATEGORY##",
9 | "common.releasedir": "##RELEASEDIR##",
10 | "common.repodir": "##REPODIR##",
11 | "common.cacert": "##CUSTOMCACERT##",
12 | "common.steps": [##RELEASESTEPS##],
13 |
14 | "common.update.method": "##UPDATEMETHOD##",
15 | "common.update.ftp.hostname": "##SFTP.HOST##",
16 | "common.update.ftp.port": "##SFTP.PORT##",
17 | "common.update.ftp.username": "##SFTP.USERNAME##",
18 | "common.update.ftp.password": "##SFTP.PASSWORD##",
19 | "common.update.ftp.passive": true,
20 | "common.update.ftp.directory": "##UPDATES_DIR##",
21 | "common.update.ftp.pubkeyfile": "##SFTP.PUBKEYFILE##",
22 | "common.update.ftp.privkeyfile": "##SFTP.PRIVKEYFILE##",
23 | "common.update.ftp.privkeyfile_pass": "##SFTP.PRIVKEYFILE.PASS##",
24 | "common.update.s3.access": "##S3.ACCESS##",
25 | "common.update.s3.secret": "##S3.SECRET##",
26 | "common.update.s3.bucket": "##S3.BUCKET##",
27 | "common.update.s3.usessl": true,
28 | "common.update.s3.signature": "##S3.SIGNATURE##",
29 | "common.update.s3.region": "##S3.REGION##",
30 | "common.update.s3.directory": "##UPDATES_DIR##",
31 | "common.update.s3.cdnhostname": "##S3.CDNHOSTNAME##",
32 |
33 | "pro.pattern": "##PRO.PATTERN##",
34 | "pro.method": "##PRO.METHOD##",
35 | "pro.update.stream": "##PRO.UPDATESTREAM##",
36 | "pro.update.formats": [##PRO.UPDATEFORMATS##],
37 | "pro.update.basename": "##PRO.UPDATEBASE##",
38 | "pro.access": "##PRO.ACCESS##",
39 | "pro.ftp.hostname": "##SFTP.HOST##",
40 | "pro.ftp.port": "##SFTP.PORT##",
41 | "pro.ftp.username": "##SFTP.USERNAME##",
42 | "pro.ftp.password": "##SFTP.PASSWORD##",
43 | "pro.ftp.passive": true,
44 | "pro.ftp.directory": "##SFTP.DIR##/##PRO.DIR##",
45 | "pro.ftp.pubkeyfile": "##SFTP.PUBKEYFILE##",
46 | "pro.ftp.privkeyfile": "##SFTP.PRIVKEYFILE##",
47 | "pro.ftp.privkeyfile_pass": "##SFTP.PRIVKEYFILE.PASS##",
48 | "pro.s3.access": "##S3.ACCESS##",
49 | "pro.s3.secret": "##S3.SECRET##",
50 | "pro.s3.bucket": "##S3.BUCKET##",
51 | "pro.s3.usessl": true,
52 | "pro.s3.signature": "##S3.SIGNATURE##",
53 | "pro.s3.region": "##S3.REGION##",
54 | "pro.s3.directory": "##PRO.DIR##",
55 | "pro.s3.cdnhostname": "##S3.CDNHOSTNAME##",
56 | "pro.s3.reldir": "##PRO.DIR##",
57 |
58 | "core.pattern": "##CORE.PATTERN##",
59 | "core.method": "##CORE.METHOD##",
60 | "core.update.stream": "##CORE.UPDATESTREAM##",
61 | "core.update.formats": [##CORE.UPDATEFORMATS##],
62 | "core.update.basename": "##CORE.UPDATEBASE##",
63 | "core.access": "##CORE.ACCESS##",
64 | "core.ftp.hostname": "##SFTP.HOST##",
65 | "core.ftp.port": "##SFTP.PORT##",
66 | "core.ftp.username": "##SFTP.USERNAME##",
67 | "core.ftp.password": "##SFTP.PASSWORD##",
68 | "core.ftp.passive": true,
69 | "core.ftp.directory": "##SFTP.DIR##/##CORE.DIR##",
70 | "core.ftp.pubkeyfile": "##SFTP.PUBKEYFILE##",
71 | "core.ftp.privkeyfile": "##SFTP.PRIVKEYFILE##",
72 | "core.ftp.privkeyfile_pass": "##SFTP.PRIVKEYFILE.PASS##",
73 | "core.s3.access": "##S3.ACCESS##",
74 | "core.s3.secret": "##S3.SECRET##",
75 | "core.s3.bucket": "##S3.BUCKET##",
76 | "core.s3.usessl": true,
77 | "core.s3.signature": "##S3.SIGNATURE##",
78 | "core.s3.region": "##S3.REGION##",
79 | "core.s3.directory": "##CORE.DIR##",
80 | "core.s3.cdnhostname": "##S3.CDNHOSTNAME##",
81 | "core.s3.reldir": "##CORE.DIR##",
82 |
83 | "pdf.where": "##DOCS.WHERE##",
84 | "pdf.files": ##DOCS.WHICH##
85 | }
86 |
--------------------------------------------------------------------------------
/phing/tasks/BladeAwfTask.php:
--------------------------------------------------------------------------------
1 | dirsets, new DirSet());
66 |
67 | return $this->dirsets[$num - 1];
68 | }
69 |
70 | public function setSite($site)
71 | {
72 | $this->site = $site;
73 |
74 | if (!is_dir($site))
75 | {
76 | throw new BuildException("The folder “{$site}” does not exist");
77 | }
78 | }
79 |
80 | /**
81 | * @param string $appName
82 | *
83 | * @return void
84 | */
85 | public function setAppName($appName)
86 | {
87 | $this->appName = $appName;
88 | }
89 |
90 | /**
91 | * @param string $awfFolder
92 | *
93 | * @return void
94 | */
95 | public function setAwfFolder(string $awfFolder)
96 | {
97 | $this->awfFolder = $awfFolder;
98 | }
99 |
100 | /**
101 | * Initialization
102 | */
103 | public function init()
104 | {
105 | return true;
106 | }
107 |
108 | /**
109 | * Main entry point for task
110 | *
111 | * @return bool
112 | *
113 | * @throws Exception
114 | */
115 | public function main()
116 | {
117 | if (count($this->dirsets) == 0)
118 | {
119 | throw new BuildException("You must specify a nested dirset");
120 | }
121 |
122 | // Include the autoloader
123 | if (!class_exists('Awf\\Autoloader\\Autoloader'))
124 | {
125 | if (false == include $this->site . '/' . $this->awfFolder . '/Autoloader/Autoloader.php')
126 | {
127 | throw new BuildException('Cannot load AWF autoloader');
128 | }
129 | }
130 |
131 | // Do not remove. Required for magic autoloading of necessary files.
132 | class_exists('\\Awf\\Utils\\Collection');
133 |
134 | // Load the platform defines
135 | if (!defined('APATH_BASE') && !class_exists('\\Awf\\Container\\Container'))
136 | {
137 | require_once $this->site . '/defines.php';
138 | }
139 |
140 | try
141 | {
142 | $container = new \Awf\Container\Container(array(
143 | 'application_name' => $this->appName
144 | ));
145 |
146 | $blade = $container->blade;
147 | }
148 | catch (Exception $e)
149 | {
150 | throw new BuildException($e->getMessage());
151 | }
152 |
153 | foreach ($this->dirsets as $dirSet)
154 | {
155 | $baseDir = $dirSet->getDir($this->project);
156 | $roots = $dirSet->getDirectoryScanner($this->project)->getIncludedDirectories();
157 |
158 | if (empty($roots))
159 | {
160 | $this->log("Empty DirSet. Skipping over.", Project::MSG_WARN);
161 |
162 | continue;
163 | }
164 |
165 | foreach ($roots as $root)
166 | {
167 | $root = $baseDir . '/' . $root;
168 |
169 | if (!is_dir($root))
170 | {
171 | $this->log("Folder “{$root}” does not exist.", Project::MSG_WARN);
172 |
173 | continue;
174 | }
175 |
176 | $root = realpath($root);
177 |
178 | $this->log("Precompiling Blade templates in folder “{$root}”.", Project::MSG_INFO);
179 |
180 | // Location of precompiled templates
181 | $outRoot = dirname($root) . '/PrecompiledTemplates';
182 |
183 | $dirIterator = new DirectoryIterator($root);
184 |
185 | // Loop View directories
186 | /** @var DirectoryIterator $viewDir */
187 | foreach ($dirIterator as $viewDir)
188 | {
189 | if (!$viewDir->isDir())
190 | {
191 | continue;
192 | }
193 |
194 | if ($viewDir->isDot())
195 | {
196 | continue;
197 | }
198 |
199 | $tmplPath = $viewDir->getRealPath();
200 | $outPath = $outRoot . '/' . $viewDir->getBasename();
201 |
202 | // Do I have to dive into a tmpl directory...?
203 | if (is_dir( $tmplPath . '/tmpl'))
204 | {
205 | // Note that $outPath is inside the PrecompiledTemplates folder, thius it never uses a tmpl subdirectory!
206 | $tmplPath .= '/tmpl';
207 | }
208 |
209 | if (!is_dir($tmplPath))
210 | {
211 | continue;
212 | }
213 |
214 | // Look for .blade.php files
215 | $tmplIterator = new DirectoryIterator($tmplPath);
216 |
217 | /** @var DirectoryIterator $file */
218 | foreach ($tmplIterator as $file)
219 | {
220 | if (!$file->isFile())
221 | {
222 | continue;
223 | }
224 |
225 | $basename = $file->getBasename();
226 |
227 | if (substr($basename, -10) != '.blade.php')
228 | {
229 | continue;
230 | }
231 |
232 | $inFile = $file->getRealPath();
233 | $outFile = $outPath . '/' . substr(basename($inFile), 0, -10) . '.php';
234 |
235 | $this->log("Precompiling $inFile to $outFile", Project::MSG_VERBOSE);
236 |
237 | try
238 | {
239 | $compiled = $blade->compile($inFile);
240 | }
241 | catch (Exception $e)
242 | {
243 | throw new BuildException("Cannot compile Blade file $inFile");
244 | }
245 |
246 | if (!is_dir($outPath))
247 | {
248 | mkdir($outPath, 0755, true);
249 | }
250 |
251 | if (!file_put_contents($outFile, $compiled))
252 | {
253 | throw new BuildException("Cannot write to pre-compiled Blade file $compiled");
254 | }
255 | }
256 | }
257 | }
258 | }
259 |
260 | return true;
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/phing/tasks/DocBookToEpubTask.php:
--------------------------------------------------------------------------------
1 | xsltRoot->getAbsolutePath() . '/epub3/chunk.xsl';
50 | $xslDoc = new DOMDocument();
51 |
52 | $this->log(sprintf("Loading XSLT file %s", $xslPath), Project::MSG_INFO);
53 |
54 | if (!$xslDoc->load($xslPath))
55 | {
56 | throw new \RuntimeException(sprintf("Cannot load XSLT file %s", $xslPath));
57 | }
58 |
59 | // Load the XML document
60 | $xmlDoc = new DOMDocument();
61 |
62 | $source = $this->docBookFile->getAbsolutePath();
63 |
64 | if (!$xmlDoc->load($source, LIBXML_DTDATTR | LIBXML_NOENT | LIBXML_NONET | LIBXML_XINCLUDE))
65 | {
66 | throw new \RuntimeException(sprintf("Cannot load DocBook XML file %s", $source));
67 | }
68 |
69 | // Apply XInclude directives (include sub-files)
70 | $xmlDoc->xinclude(LIBXML_DTDATTR | LIBXML_NOENT | LIBXML_NONET | LIBXML_XINCLUDE);
71 |
72 | // Setup the XSLT processor
73 | $parameters = array(
74 | 'base.dir' => $this->epubPath->getAbsolutePath() . '/OEBPS/',
75 | 'epub.stylesheet' => 'style.css',
76 | 'body.start.indent' => 0,
77 | 'variablelist.term.break.after' => 1,
78 | 'variablelist.term.separator' => '""',
79 | 'variablelist.max.termlength' => 12,
80 | 'admon.graphics' => 1,
81 | 'use.id.as.filename' => 1,
82 | 'chunk.section.depth' => 3,
83 | 'section.autolabel' => 1,
84 | 'toc.section.depth' => 5,
85 | 'highlight.source' => 1,
86 | 'paper.type' => 'A4',
87 | );
88 |
89 | $xslt = new XSLTProcessor();
90 | $xslt->importStylesheet($xslDoc);
91 |
92 | if (!$xslt->setParameter('', $parameters))
93 | {
94 | throw new \RuntimeException("Cannot set XSLTProcessor parameters");
95 | }
96 |
97 | // Process it!
98 | set_time_limit(0);
99 |
100 | $oldval = $xslt->setSecurityPrefs(XSL_SECPREF_NONE);
101 | $result = $xslt->transformToXml($xmlDoc);
102 |
103 | $xslt->setSecurityPrefs($oldval);
104 |
105 | unset($xslt);
106 |
107 | if ($result === false)
108 | {
109 | throw new \RuntimeException(sprintf("Failed to process DocBook XML file %s", $source));
110 | }
111 | }
112 |
113 | /**
114 | * Set the xsltRoot property: absolute path with the DocBook XSL Stylesheets distribution
115 | *
116 | * @param File $xsltRoot
117 | *
118 | * @return void
119 | */
120 | public function setXsltRoot(File $xsltRoot)
121 | {
122 | $this->xsltRoot = $xsltRoot;
123 | }
124 |
125 | /**
126 | * Set the source property: absolute filename of the DocBook XML file to process
127 | *
128 | * @param File $docBookFile
129 | *
130 | * @return void
131 | */
132 | public function setDocBookFile(File $docBookFile)
133 | {
134 | $this->docBookFile = $docBookFile;
135 | }
136 |
137 | /**
138 | * Set the target property: absolute filename of the resulting .fo file
139 | *
140 | * @param File $epubPath
141 | *
142 | * @return void
143 | */
144 | public function setEpubPath(File $epubPath)
145 | {
146 | $this->epubPath = $epubPath;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/phing/tasks/GitDateTask.php:
--------------------------------------------------------------------------------
1 | workingCopy = $workingCopy;
43 | }
44 |
45 | /**
46 | * Returns the path to the working copy
47 | *
48 | * @return string
49 | */
50 | public function getWorkingCopy()
51 | {
52 | return $this->workingCopy;
53 | }
54 |
55 | /**
56 | * Sets the name of the property to use
57 | *
58 | * @param string $propertyName
59 | */
60 | function setPropertyName($propertyName)
61 | {
62 | $this->propertyName = $propertyName;
63 | }
64 |
65 | /**
66 | * Returns the name of the property to use
67 | *
68 | * @return string
69 | */
70 | function getPropertyName()
71 | {
72 | return $this->propertyName;
73 | }
74 |
75 | /**
76 | * Gets the date format
77 | *
78 | * @return string
79 | */
80 | function getFormat()
81 | {
82 | return $this->format;
83 | }
84 |
85 | /**
86 | * Sets the date format
87 | *
88 | * @param string $format
89 | */
90 | function setFormat($format)
91 | {
92 | $this->format = $format;
93 | }
94 |
95 | /**
96 | * The main entry point
97 | *
98 | * @throws \Phing\Exception\BuildException
99 | */
100 | function main()
101 | {
102 | if ($this->workingCopy == '..')
103 | {
104 | $this->workingCopy = '../';
105 | }
106 |
107 | $cwd = getcwd();
108 | $this->workingCopy = realpath($this->workingCopy);
109 |
110 | chdir($this->workingCopy);
111 | exec('git log --format=%at -n1 ' . escapeshellarg($this->workingCopy), $timestamp);
112 | chdir($cwd);
113 |
114 | $date = date($this->format, trim($timestamp[0]));
115 | $this->project->setProperty($this->getPropertyName(), $date);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/phing/tasks/GitHubAssetTask.php:
--------------------------------------------------------------------------------
1 | getFile();
83 | $releaseId = $this->getReleaseId();
84 | $asset = $this->client
85 | ->api('repo')
86 | ->releases()
87 | ->assets()
88 | ->create(
89 | $this->organization, $this->repository, $releaseId,
90 | $this->getRemoteName(), $this->getContentType(), file_get_contents($fileName));
91 |
92 | $assetId = $asset['id'];
93 | $assetUrl = $asset['browser_download_url'];
94 |
95 | $this->log(sprintf('Created asset for release %d, source file %s (ID#: %d)', $releaseId, $this->getFile(), $assetId));
96 |
97 | if (!empty($this->idProperty))
98 | {
99 | $this->log(sprintf('Assigning asset ID to property %s', $this->idProperty));
100 | $this->project->setProperty($this->idProperty, $assetId);
101 | }
102 |
103 | if (!empty($this->downloadProperty))
104 | {
105 | $this->log(sprintf('Assigning download URL to property %s', $this->downloadProperty));
106 | $this->project->setProperty($this->downloadProperty, $assetUrl);
107 | }
108 | }
109 |
110 | /**
111 | * Set the Release ID we're uploading to
112 | *
113 | * @param int $releaseId
114 | *
115 | * @return void
116 | */
117 | public function setReleaseId(int $releaseId)
118 | {
119 | $this->releaseId = $releaseId;
120 | }
121 |
122 | /**
123 | * Set the MIME content type of the uploaded file
124 | *
125 | * @param string $contentType
126 | *
127 | * @return void
128 | */
129 | public function setContentType(string $contentType)
130 | {
131 | $this->contentType = $contentType;
132 | }
133 |
134 | /**
135 | * Set the filename on GitHub
136 | *
137 | * @param string $remoteName
138 | *
139 | * @return void
140 | */
141 | public function setRemoteName(string $remoteName)
142 | {
143 | $this->remoteName = $remoteName;
144 | }
145 |
146 | /**
147 | * Set the label which will be used instead of the filename on GitHub
148 | *
149 | * @param string $label
150 | *
151 | * @return void
152 | */
153 | public function setLabel(string $label)
154 | {
155 | $this->label = $label;
156 | }
157 |
158 | /**
159 | * Set the file to upload
160 | *
161 | * @param string $file
162 | *
163 | * @return void
164 | */
165 | public function setFile(string $file)
166 | {
167 | $this->file = $file;
168 | }
169 |
170 | /**
171 | * Set the Phing property to save the public download URL to
172 | *
173 | * @param string $downloadProperty
174 | *
175 | * @return void
176 | */
177 | public function setDownloadProperty(string $downloadProperty)
178 | {
179 | $this->downloadProperty = $downloadProperty;
180 | }
181 |
182 | /**
183 | * Set the Phing property to save the asset ID to
184 | *
185 | * @param string $idProperty
186 | *
187 | * @return void
188 | */
189 | public function setIdProperty(string $idProperty)
190 | {
191 | $this->idProperty = $idProperty;
192 | }
193 |
194 | /**
195 | * Return the configured Release ID after making some sanity checks
196 | *
197 | * @return int
198 | */
199 | public function getReleaseId()
200 | {
201 | if (!isset($this->releaseId))
202 | {
203 | throw new BuildException("You must set the Release ID to upload an asset to it.");
204 | }
205 |
206 | if ($this->releaseId <= 0)
207 | {
208 | throw new BuildException("The Release ID must be a positive integer. You specified: {$this->releaseId}");
209 | }
210 |
211 | return $this->releaseId;
212 | }
213 |
214 | /**
215 | * Return the MIME content type of the file being uploaded. If none was specified we will try to detect it.
216 | *
217 | * @return string
218 | */
219 | public function getContentType(): string
220 | {
221 | if (empty($this->contentType))
222 | {
223 | $fileName = $this->getFile();
224 | $fileInfo = finfo_open(FILEINFO_MIME_TYPE);
225 | $this->contentType = finfo_file($fileInfo, $fileName);
226 | finfo_close($fileInfo);
227 |
228 | if (empty($this->contentType))
229 | {
230 | throw new BuildException("Cannot detect content type for file $fileName");
231 | }
232 | }
233 |
234 | return $this->contentType;
235 | }
236 |
237 | /**
238 | * Returns the file to upload after making some sanity checks
239 | *
240 | * @return string
241 | */
242 | public function getFile(): string
243 | {
244 | if (empty($this->file))
245 | {
246 | throw new BuildException("You must specify which file you want to upload to GitHub.");
247 | }
248 |
249 | if (!file_exists($this->file))
250 | {
251 | throw new BuildException("The file $this->file does not exist, so it cannot be uploaded to GitHub.");
252 | }
253 |
254 | return $this->file;
255 | }
256 |
257 | /**
258 | * Get the filename as it will be reported by GitHub
259 | *
260 | * @return string
261 | */
262 | public function getRemoteName(): string
263 | {
264 | if (empty($this->remoteName))
265 | {
266 | $file = $this->getFile();
267 |
268 | $this->remoteName = basename($file);
269 | }
270 |
271 | return $this->remoteName;
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/phing/tasks/GitVersionTask.php:
--------------------------------------------------------------------------------
1 | workingCopy = $workingCopy;
49 | }
50 |
51 | /**
52 | * Returns the path to the working copy
53 | *
54 | * @return string
55 | */
56 | public function getWorkingCopy()
57 | {
58 | return $this->workingCopy;
59 | }
60 |
61 | /**
62 | * Sets the name of the property to use
63 | *
64 | * @param string $propertyName
65 | */
66 | function setPropertyName($propertyName)
67 | {
68 | $this->propertyName = $propertyName;
69 | }
70 |
71 | /**
72 | * Returns the name of the property to use
73 | *
74 | * @return string
75 | */
76 | function getPropertyName()
77 | {
78 | return $this->propertyName;
79 | }
80 |
81 | /**
82 | * The main entry point
83 | *
84 | * @throws BuildException
85 | */
86 | function main()
87 | {
88 | if ($this->workingCopy == '..')
89 | {
90 | $this->workingCopy = '../';
91 | }
92 |
93 | $cwd = getcwd();
94 | $this->workingCopy = realpath($this->workingCopy);
95 |
96 | chdir($this->workingCopy);
97 | exec('git log --format=%h -n1 ' . escapeshellarg(realpath($this->workingCopy)), $out);
98 | chdir($cwd);
99 |
100 | $this->project->setProperty($this->getPropertyName(), strtoupper(trim($out[0])));
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/phing/tasks/JPATask.php:
--------------------------------------------------------------------------------
1 | fileset = new JpaFileSet();
69 | $this->filesets[] = $this->fileset;
70 |
71 | return $this->fileset;
72 | }
73 |
74 | /**
75 | * Add a new fileset.
76 | *
77 | * @return JpaFileSet
78 | */
79 | public function createJpaFileSet()
80 | {
81 | $this->fileset = new JpaFileSet();
82 | $this->filesets[] = $this->fileset;
83 |
84 | return $this->fileset;
85 | }
86 |
87 | /**
88 | * Set the name/location of where to create the JPA file.
89 | *
90 | * @param File $destFile The location of the output JPA file
91 | */
92 | public function setDestFile(File $destFile)
93 | {
94 | $this->jpaFile = $destFile;
95 | }
96 |
97 | /**
98 | * Set the include empty directories flag.
99 | *
100 | * @param boolean $bool Should empty directories be included in the archive?
101 | *
102 | * @return void
103 | */
104 | public function setIncludeEmptyDirs($bool)
105 | {
106 | $this->includeEmpty = (boolean) $bool;
107 | }
108 |
109 | /**
110 | * This is the base directory to look in for files to archive.
111 | *
112 | * @param File $baseDir The base directory to scan
113 | *
114 | * @return void
115 | */
116 | public function setBasedir(File $baseDir)
117 | {
118 | $this->baseDir = $baseDir;
119 | }
120 |
121 | /**
122 | * Sets the file path prefix for files in the JPA archive
123 | *
124 | * @param string $prefix Prefix
125 | *
126 | * @return void
127 | */
128 | public function setPrefix(string $prefix)
129 | {
130 | $this->prefix = $prefix;
131 | }
132 |
133 | /**
134 | * do the work
135 | *
136 | * @throws BuildException
137 | */
138 | public function main()
139 | {
140 | if ($this->jpaFile === null)
141 | {
142 | throw new BuildException("jpafile attribute must be set!", $this->getLocation());
143 | }
144 |
145 | if ($this->jpaFile->exists() && $this->jpaFile->isDirectory())
146 | {
147 | throw new BuildException("jpafile is a directory!", $this->getLocation());
148 | }
149 |
150 | if ($this->jpaFile->exists() && !$this->jpaFile->canWrite())
151 | {
152 | throw new BuildException("Can not write to the specified jpafile!", $this->getLocation());
153 | }
154 |
155 | $savedFileSets = $this->filesets;
156 |
157 | try
158 | {
159 | if (empty($this->filesets))
160 | {
161 | throw new BuildException(
162 | "You must supply some nested filesets.", $this->getLocation()
163 | );
164 | }
165 |
166 | $this->log("Building JPA: " . $this->jpaFile->__toString(), Project::MSG_INFO);
167 |
168 | $jpa = new JPAMaker();
169 | $jpa->create($this->jpaFile->getAbsolutePath());
170 |
171 | foreach ($this->filesets as $fs)
172 | {
173 | $files = $fs->getFiles($this->project, $this->includeEmpty);
174 | $fsBasedir = (null != $this->baseDir) ? $this->baseDir : $fs->getDir($this->project);
175 |
176 | foreach ($files as $file)
177 | {
178 | $f = new File($fsBasedir, $file);
179 |
180 | $pathInJPA = $this->prefix . $f->getPathWithoutBase($fsBasedir);
181 | $jpa->addFile($f->getPath(), $pathInJPA);
182 | $this->log("Adding " . $f->getPath() . " as " . $pathInJPA . " to archive.", Project::MSG_VERBOSE);
183 | }
184 | }
185 |
186 | $jpa->finalize();
187 | }
188 | catch (IOException $ioe)
189 | {
190 | $msg = "Problem creating JPA: " . $ioe->getMessage();
191 | $this->filesets = $savedFileSets;
192 |
193 | throw new BuildException($msg, $ioe, $this->getLocation());
194 | }
195 |
196 | $this->filesets = $savedFileSets;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/phing/tasks/LinkTask.php:
--------------------------------------------------------------------------------
1 |
23 | *
24 | *
25 | *
26 | * Link entire contents of directory
27 | *
28 | * This will go through the contents of "/my/shared/library/*"
29 | * and create a link for each entry into ${project.basedir}/library/
30 | *
31 | *
32 | *
33 | *
34 | *
35 | *
36 | *
37 | *
38 | * The type property can be used to override the link type, which is by default a
39 | * symlink. Example for a hardlink:
40 | *
41 | *
42 | *
43 | */
44 | class LinkTask extends SymlinkTask
45 | {
46 | /**
47 | * Link type.
48 | *
49 | * @var string
50 | */
51 | protected $type = 'symlink';
52 |
53 | /**
54 | * Setter for _type.
55 | *
56 | * @param string $type
57 | */
58 | public function setType($type)
59 | {
60 | $this->type = $type;
61 | }
62 |
63 | /**
64 | * Getter for _type.
65 | *
66 | * @return string
67 | */
68 | public function getType()
69 | {
70 | return $this->type;
71 | }
72 |
73 | /**
74 | * Main entry point for task
75 | *
76 | * @return bool
77 | */
78 | public function main()
79 | {
80 | $map = $this->getMap();
81 | $to = $this->getLink();
82 | $type = $this->getType();
83 |
84 | // Single file symlink
85 | if (is_string($map))
86 | {
87 | $from = $map;
88 | LinkHelper::makeLink($from, $to, $type);
89 |
90 | return true;
91 | }
92 |
93 | // Multiple symlinks
94 | foreach ($map as $name => $from)
95 | {
96 | $realTo = $to . DIRECTORY_SEPARATOR . $name;
97 | LinkHelper::makeLink($from, $realTo, $type);
98 | }
99 |
100 | return true;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/phing/tasks/PhpStormSources.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function main()
39 | {
40 | $this->log("Processing PhpStorm sources for " . $this->repository, Project::MSG_INFO);
41 |
42 | if (!class_exists(PhpStormSourceHandling::class))
43 | {
44 | defined('AKEEBA_PHPSTORMSOURCES_INCLUDE_ONLY') || define('AKEEBA_PHPSTORMSOURCES_INCLUDE_ONLY', 1);
45 |
46 | require_once __DIR__ . '/../../clitools/phpStormSourceHandling.php';
47 | }
48 |
49 | $o = new PhpStormSourceHandling();
50 |
51 | $o->execute($this->repository, true);
52 |
53 | return true;
54 | }
55 | }
--------------------------------------------------------------------------------
/phing/tasks/ProjectLinkTask.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | class ProjectLinkTask extends Task
28 | {
29 | /**
30 | * The path to the repository containing all the extensions
31 | *
32 | * @var string
33 | */
34 | private $repository = null;
35 |
36 | /**
37 | * Set the repository root folder
38 | *
39 | * @param string $repository The new repository root folder
40 | *
41 | * @return void
42 | */
43 | public function setRepository(string $repository)
44 | {
45 | $this->repository = $repository;
46 | }
47 |
48 | /**
49 | * Main entry point for task.
50 | *
51 | * @return bool
52 | */
53 | public function main()
54 | {
55 | $this->log("Relinking " . $this->repository, Project::MSG_INFO);
56 |
57 | if (empty($this->repository))
58 | {
59 | $this->repository = realpath($this->project->getBasedir() . '/../..');
60 | }
61 |
62 | if (!is_dir($this->repository))
63 | {
64 | throw new BuildException("Repository folder {$this->repository} is not a valid directory");
65 | }
66 |
67 | $linker = new ProjectLinker($this->repository);
68 |
69 | try
70 | {
71 | $linker->addInternalLanguageMapping();
72 | }
73 | catch (RuntimeException $e)
74 | {
75 | // If you run this before `phing git` creates the XML manifest this fails. So we need to swallow the error.
76 | }
77 |
78 | $linker->link();
79 |
80 | return true;
81 | }
82 |
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/phing/tasks/RelinkSiteTask.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class RelinkSiteTask extends Task
26 | {
27 | /**
28 | * The path to the repository containing all the extensions
29 | *
30 | * @var string
31 | */
32 | private $repository = null;
33 |
34 | /**
35 | * The path to the site's root.
36 | *
37 | * @var string
38 | */
39 | private $site = null;
40 |
41 | /**
42 | * Set the site root folder
43 | *
44 | * @param string $siteRoot The new site root
45 | *
46 | * @return void
47 | */
48 | public function setSite($siteRoot)
49 | {
50 | $this->site = $siteRoot;
51 | }
52 |
53 | /**
54 | * Set the repository root folder
55 | *
56 | * @param string $repository The new repository root folder
57 | *
58 | * @return void
59 | */
60 | public function setRepository(string $repository)
61 | {
62 | $this->repository = $repository;
63 | }
64 |
65 | /**
66 | * Main entry point for task.
67 | *
68 | * @return bool
69 | */
70 | public function main()
71 | {
72 | $this->log("Processing links for " . $this->site, Project::MSG_INFO);
73 |
74 | if (empty($this->repository))
75 | {
76 | $this->repository = realpath($this->project->getBasedir() . '/../..');
77 | }
78 |
79 | if (in_array(substr($this->site, 0, 2), ['~/', '~' . DIRECTORY_SEPARATOR]))
80 | {
81 | $home = $this->getUserHomeDirectory();
82 |
83 | if (is_null($home))
84 | {
85 | throw new BuildException(
86 | "Site root folder {$this->site} cannot be resolved: your environment does not return information on the user's Home folder location."
87 | );
88 | }
89 |
90 | $this->site = $home . DIRECTORY_SEPARATOR . substr($this->site, 2);
91 | }
92 |
93 | if (!is_dir($this->site))
94 | {
95 | throw new BuildException("Site root folder {$this->site} is not a valid directory");
96 | }
97 |
98 | if (!is_dir($this->repository))
99 | {
100 | throw new BuildException("Repository folder {$this->repository} is not a valid directory");
101 | }
102 |
103 | @error_reporting(E_ALL);
104 | try
105 | {
106 | $relink = new Relink($this->repository);
107 | $relink->setVerbose(true);
108 | $relink->relink($this->site);
109 | }
110 | catch (Throwable $e)
111 | {
112 | echo $e->getMessage();
113 |
114 | die;
115 | }
116 |
117 | return true;
118 | }
119 |
120 | /**
121 | * Returns the currently logged in OS user's home directory absolute path
122 | *
123 | * @return string|null Home directory absolute path. NULL if it cannot be determined.
124 | */
125 | function getUserHomeDirectory(): ?string
126 | {
127 | // Try the UNIX method first. If it fails it will return either false or null. Normalize it to NULL.
128 | $home = @getenv('HOME');
129 | $home = ($home === false) ? null : $home;
130 |
131 | // Fallback to Windows method for determining the home
132 | if (is_null($home) && !empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH']))
133 | {
134 | $home = $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH'];
135 | }
136 |
137 | // Early exit if everything failed
138 | if (is_null($home))
139 | {
140 | return $home;
141 | }
142 |
143 | // Remove the trailing slash / backslash
144 | return rtrim($home, '/' . DIRECTORY_SEPARATOR);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/phing/tasks/RelinkWPSiteTask.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class RelinkWPSiteTask extends Task
26 | {
27 | /**
28 | * The path to the repository containing the plugin
29 | *
30 | * @var string
31 | */
32 | private $repository = null;
33 |
34 | /**
35 | * The path to the site's root.
36 | *
37 | * @var string
38 | */
39 | private $site = null;
40 |
41 | /**
42 | * Set the site root folder
43 | *
44 | * @param string $siteRoot The new site root
45 | *
46 | * @return void
47 | */
48 | public function setSite($siteRoot)
49 | {
50 | $this->site = $siteRoot;
51 | }
52 |
53 | /**
54 | * Set the repository root folder
55 | *
56 | * @param string $repository The new repository root folder
57 | *
58 | * @return void
59 | */
60 | public function setRepository(string $repository)
61 | {
62 | $this->repository = $repository;
63 | }
64 |
65 | /**
66 | * Main entry point for task.
67 | *
68 | * @return bool
69 | */
70 | public function main()
71 | {
72 | $this->log("Processing links for " . $this->site, Project::MSG_INFO);
73 |
74 | if (empty($this->repository))
75 | {
76 | $this->repository = realpath($this->project->getBasedir() . '/../..');
77 | }
78 |
79 | if (in_array(substr($this->site, 0, 2), ['~/', '~' . DIRECTORY_SEPARATOR]))
80 | {
81 | $home = $this->getUserHomeDirectory();
82 |
83 | if (is_null($home))
84 | {
85 | throw new BuildException(
86 | "Site root folder {$this->site} cannot be resolved: your environment does not return information on the user's Home folder location."
87 | );
88 | }
89 |
90 | $this->site = $home . DIRECTORY_SEPARATOR . substr($this->site, 2);
91 | }
92 |
93 | if (!is_dir($this->site))
94 | {
95 | throw new BuildException("Site root folder {$this->site} is not a valid directory");
96 | }
97 |
98 | if (!is_dir($this->repository))
99 | {
100 | throw new BuildException("Repository folder {$this->repository} is not a valid directory");
101 | }
102 |
103 | if (!class_exists(ProjectLinker::class))
104 | {
105 | require_once __DIR__ . '/../../vendor/autoload.php';
106 | }
107 |
108 | $linker = new ProjectLinker();
109 | $linker->setRepositoryRoot('.');
110 |
111 | include $this->repository . '/build/templates/relink-wp.php';
112 |
113 | if (isset($hardlink_files) && is_array($hardlink_files) && !empty($hardlink_files))
114 | {
115 | $linker->setHardlinkFiles($this->processPathArray($hardlink_files));
116 | }
117 |
118 | if (isset($symlink_files) && is_array($symlink_files) && !empty($symlink_files))
119 | {
120 | $linker->setSymlinkFiles($this->processPathArray($symlink_files));
121 | }
122 |
123 | if (isset($symlink_folders) && is_array($symlink_folders) && !empty($symlink_folders))
124 | {
125 | $linker->setSymlinkFolders($this->processPathArray($symlink_folders));
126 | }
127 |
128 | $linker->link();
129 |
130 | return true;
131 | }
132 |
133 | /**
134 | * Process a path array which is in the form
135 | * relative repo path => relative site path
136 | * into the form
137 | * relative repo path => ABSOLUTE site path
138 | *
139 | * @param array $pathArray
140 | *
141 | * @return array
142 | */
143 | private function processPathArray(array $pathArray): array
144 | {
145 | $temp = [];
146 |
147 | foreach ($pathArray as $local => $sitePath)
148 | {
149 | $local = realpath($this->repository . '/' . $local);
150 |
151 | if (empty($local))
152 | {
153 | continue;
154 | }
155 |
156 | $temp[$local] = $this->site . '/' . $sitePath;
157 | }
158 |
159 | return $temp;
160 | }
161 |
162 | /**
163 | * Returns the currently logged in OS user's home directory absolute path
164 | *
165 | * @return string|null Home directory absolute path. NULL if it cannot be determined.
166 | */
167 | function getUserHomeDirectory(): ?string
168 | {
169 | // Try the UNIX method first. If it fails it will return either false or null. Normalize it to NULL.
170 | $home = @getenv('HOME');
171 | $home = ($home === false) ? null : $home;
172 |
173 | // Fallback to Windows method for determining the home
174 | if (is_null($home) && !empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH']))
175 | {
176 | $home = $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH'];
177 | }
178 |
179 | // Early exit if everything failed
180 | if (is_null($home))
181 | {
182 | return $home;
183 | }
184 |
185 | // Remove the trailing slash / backslash
186 | return rtrim($home, '/' . DIRECTORY_SEPARATOR);
187 | }
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/phing/tasks/WordPressEntryPointTask.php:
--------------------------------------------------------------------------------
1 | file))
28 | {
29 | throw new BuildException('You must specify a WordPress entry point file to modify.');
30 | }
31 |
32 | if (!is_file($this->file))
33 | {
34 | throw new BuildException(sprintf('%s is not a file.', $this->file));
35 | }
36 |
37 | // Get the replacements
38 | $replacements = $this->getReplacements();
39 |
40 | if (empty($replacements))
41 | {
42 | return;
43 | }
44 |
45 | // Read the file
46 | $contents = @file_get_contents($this->file);
47 |
48 | if ($contents === false)
49 | {
50 | throw new BuildException(sprintf('%s cannot be read from.', $this->file));
51 | }
52 |
53 | // Replace the contents in the file
54 | $contents = $this->replace($contents, $replacements);
55 |
56 | // Write the results back to the file
57 | $result = @file_put_contents($this->file, $contents);
58 |
59 | if ($result === false)
60 | {
61 | throw new BuildException(sprintf('%s cannot be written to.', $this->file));
62 | }
63 | }
64 |
65 | public function setFile(string $file)
66 | {
67 | $this->file = $file;
68 | }
69 |
70 | public function setVersion(string $version)
71 | {
72 | $this->version = $version;
73 | }
74 |
75 | public function setName(string $name)
76 | {
77 | $this->name = $name;
78 | }
79 |
80 | private function getReplacements(): array
81 | {
82 | $ret = [];
83 |
84 | if ($this->name !== null)
85 | {
86 | $ret['Plugin Name'] = $this->name;
87 | }
88 |
89 | if ($this->version !== null)
90 | {
91 | $ret['Version'] = $this->version;
92 | }
93 |
94 | return $ret;
95 | }
96 |
97 | private function replace(string $contents, array $replacements)
98 | {
99 | $lines = array_map(
100 | function (string $line) use ($replacements): string {
101 | foreach ($replacements as $key => $newValue)
102 | {
103 | $test = $key . ':';
104 |
105 | if (strpos($line, $test) === 0)
106 | {
107 | return sprintf('%s: %s', $key, $newValue);
108 | }
109 | }
110 |
111 | return $line;
112 | }, explode("\n", $contents)
113 | );
114 |
115 | return implode("\n", $lines);
116 | }
117 |
118 | }
--------------------------------------------------------------------------------
/phing/tasks/XmlUpdateTask.php:
--------------------------------------------------------------------------------
1 |
21 | * ```
22 | */
23 | class XmlUpdateTask extends Task
24 | {
25 |
26 | private ?string $xml = null;
27 |
28 | private ?string $tofile = null;
29 |
30 | public function setXml(string $xml)
31 | {
32 | $this->xml = $xml;
33 | }
34 |
35 | public function setTofile(string $tofile)
36 | {
37 | $this->tofile = $tofile;
38 | }
39 |
40 | /**
41 | * @inheritDoc
42 | */
43 | public function main()
44 | {
45 | $xml = new DOMDocument();
46 | $xml->loadXML($this->xml);
47 | $versions = [];
48 |
49 | /** @var DOMNode $updateItem */
50 | foreach ($xml->documentElement->getElementsByTagName('update') as $updateItem)
51 | {
52 | /** @var DOMNode $subNode */
53 | foreach ($updateItem->childNodes as $subNode)
54 | {
55 | if ($subNode->nodeName === 'version')
56 | {
57 | $version = trim($subNode->textContent);
58 |
59 | if (!in_array($version, $versions))
60 | {
61 | $versions[] = $version;
62 | }
63 | else
64 | {
65 | $xml->documentElement->removeChild($updateItem);
66 | }
67 |
68 | continue 2;
69 | }
70 | }
71 | }
72 |
73 | $generated = $xml->saveXML();
74 |
75 | $generated = preg_replace_callback(
76 | '#(.*)#s', function (array $matches) {
77 | $internal = str_replace("\n", "", $matches[0]);
78 |
79 | $internal = preg_replace('#\s{2,}#', ' ', $internal);
80 | $internal = preg_replace('#> <#', '><', $internal);
81 |
82 | return $internal;
83 | }, $generated
84 | );
85 |
86 | file_put_contents($this->tofile, $generated);
87 | }
88 | }
--------------------------------------------------------------------------------
/phing/tasks/XmlVersionTask.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | class XmlVersionTask extends Task
25 | {
26 | /**
27 | * The path to the repository containing all the extensions
28 | *
29 | * @var string
30 | */
31 | private $repository = null;
32 |
33 | private $version = null;
34 |
35 | private $date = null;
36 |
37 | /**
38 | * Set the repository root folder
39 | *
40 | * @param string $repository The new repository root folder
41 | *
42 | * @return void
43 | */
44 | public function setRepository(string $repository)
45 | {
46 | $this->repository = $repository;
47 | }
48 |
49 | public function setVersion(string $version)
50 | {
51 | $this->version = $version;
52 | }
53 |
54 | public function setDate(string $date)
55 | {
56 | $this->date = $date;
57 | }
58 |
59 | private function scan($baseDir, $level = 0)
60 | {
61 | if (!is_dir($baseDir))
62 | {
63 | return;
64 | }
65 |
66 | $di = new DirectoryIterator($baseDir);
67 |
68 | /** @var DirectoryIterator $entry */
69 | foreach ($di as $entry)
70 | {
71 | if ($entry->isDot())
72 | {
73 | continue;
74 | }
75 |
76 | if ($entry->isLink())
77 | {
78 | continue;
79 | }
80 |
81 | if ($entry->isDir())
82 | {
83 | if ($level < 4)
84 | {
85 | $this->scan($entry->getPathname(), $level + 1);
86 | }
87 |
88 | continue;
89 | }
90 |
91 | if (!$entry->isFile() || !$entry->isReadable())
92 | {
93 | continue;
94 | }
95 |
96 | switch ($entry->getExtension())
97 | {
98 | case 'xml':
99 | echo $entry->getPathname();
100 |
101 | $result = $this->convert($entry->getPathname());
102 |
103 | echo $result ? " -- CONVERTED\n" : " -- (invalid)\n";
104 | break;
105 |
106 | case 'json':
107 | if ($entry->getBasename() != 'joomla.asset.json')
108 | {
109 | continue 2;
110 | }
111 |
112 | echo $entry->getPathname();
113 |
114 | $result = $this->convertJSON($entry->getPathname());
115 |
116 | echo $result ? " -- CONVERTED\n" : " -- (invalid)\n";
117 | break;
118 |
119 | default:
120 | continue 2;
121 | }
122 |
123 | }
124 | }
125 |
126 | private function convert($filePath)
127 | {
128 | $fileData = file_get_contents($filePath);
129 |
130 | if (strpos($fileData, '.*#';
136 | $replacement = "{$this->date}";
137 | $fileData = preg_replace($pattern, $replacement, $fileData);
138 |
139 | if (is_null($fileData))
140 | {
141 | return false;
142 | }
143 |
144 | $pattern = '#.*#';
145 | $replacement = "{$this->version}";
146 | $fileData = preg_replace($pattern, $replacement, $fileData);
147 |
148 | if (is_null($fileData))
149 | {
150 | return false;
151 | }
152 |
153 | file_put_contents($filePath, $fileData);
154 |
155 | return true;
156 | }
157 |
158 | private function convertJSON(string $filePath)
159 | {
160 | $fileData = @file($filePath);
161 |
162 | $fileData = array_map(
163 | function ($line) {
164 | if ((strpos(trim($line), '"version"') === 0)
165 | && (strpos($line, ':') !== false))
166 | {
167 | $parts = explode(':', $line, 2);
168 | $parts[1] = sprintf('"%s",', $this->version);
169 | $line = implode(': ', $parts);
170 | }
171 |
172 | return rtrim($line, "\n\r");
173 | }, $fileData
174 | );
175 |
176 | file_put_contents($filePath, implode("\n", $fileData));
177 |
178 | return true;
179 | }
180 |
181 | /**
182 | * Main entry point for task.
183 | *
184 | * @return bool
185 | */
186 | public function main()
187 | {
188 | $this->log("Modifying XML manifests under " . $this->repository, Project::MSG_INFO);
189 |
190 | if (empty($this->repository))
191 | {
192 | $this->repository = realpath($this->project->getBasedir() . '/../..');
193 | }
194 |
195 | if (!is_dir($this->repository))
196 | {
197 | throw new BuildException("Repository folder {$this->repository} is not a valid directory");
198 | }
199 |
200 | $paths = [
201 | realpath($this->repository) . '/component',
202 | realpath($this->repository) . '/plugins',
203 | realpath($this->repository) . '/modules',
204 | realpath($this->repository) . '/templates/admin',
205 | realpath($this->repository) . '/templates/site',
206 | ];
207 |
208 | array_walk($paths, [$this, 'scan']);
209 |
210 | return true;
211 | }
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/phing/tasks/ZipmeTask.php:
--------------------------------------------------------------------------------
1 | fileset = new ZipmeFileSet();
73 | $this->filesets[] = $this->fileset;
74 |
75 | return $this->fileset;
76 | }
77 |
78 | /**
79 | * Add a new fileset.
80 | *
81 | * @return FileSet
82 | */
83 | public function createZipmeFileSet()
84 | {
85 | $this->fileset = new ZipmeFileSet();
86 | $this->filesets[] = $this->fileset;
87 |
88 | return $this->fileset;
89 | }
90 |
91 | /**
92 | * Set the name/location of where to create the JPA file.
93 | *
94 | * @param File $destFile The location of the output JPA file
95 | */
96 | public function setDestFile(File $destFile)
97 | {
98 | $this->zipFile = $destFile;
99 | }
100 |
101 | /**
102 | * Set the include empty directories flag.
103 | *
104 | * @param boolean $bool Should empty directories be included in the archive?
105 | *
106 | * @return void
107 | */
108 | public function setIncludeEmptyDirs($bool)
109 | {
110 | $this->includeEmpty = (boolean) $bool;
111 | }
112 |
113 | /**
114 | * This is the base directory to look in for files to archive.
115 | *
116 | * @param File $baseDir The base directory to scan
117 | *
118 | * @return void
119 | */
120 | public function setBasedir(File $baseDir)
121 | {
122 | $this->baseDir = $baseDir;
123 | }
124 |
125 | /**
126 | * Sets the file path prefix for files in the JPA archive
127 | *
128 | * @param string $prefix Prefix
129 | *
130 | * @return void
131 | */
132 | public function setPrefix(string $prefix)
133 | {
134 | $this->prefix = $prefix;
135 | }
136 |
137 | /**
138 | * Do the work
139 | *
140 | * @throws BuildException
141 | */
142 | public function main()
143 | {
144 | if ($this->zipFile === null)
145 | {
146 | throw new BuildException("zipFile attribute must be set!", $this->getLocation());
147 | }
148 |
149 | if ($this->zipFile->exists() && $this->zipFile->isDirectory())
150 | {
151 | throw new BuildException("zipFile is a directory!", $this->getLocation());
152 | }
153 |
154 | if ($this->zipFile->exists() && !$this->zipFile->canWrite())
155 | {
156 | throw new BuildException("Can not write to the specified zipFile!", $this->getLocation());
157 | }
158 |
159 | $savedFileSets = $this->filesets;
160 |
161 | try
162 | {
163 | if (empty($this->filesets))
164 | {
165 | throw new BuildException("You must supply some nested filesets.", $this->getLocation());
166 | }
167 |
168 | $this->log("Building ZIP: " . $this->zipFile->__toString(), Project::MSG_INFO);
169 |
170 | $absolutePath = $this->zipFile->getAbsolutePath();
171 |
172 | if (!is_dir(dirname($absolutePath)))
173 | {
174 | throw new BuildException("ZIP file path $absolutePath is not a path.", $this->getLocation());
175 | }
176 |
177 | $zip = new ZipArchive();
178 | $openResult = $zip->open($this->zipFile->getAbsolutePath(), ZipArchive::CREATE);
179 |
180 | if ($openResult !== true)
181 | {
182 | switch ($openResult)
183 | {
184 | case ZipArchive::ER_EXISTS:
185 | $message = 'File already exists.';
186 | break;
187 |
188 | case ZipArchive::ER_INCONS:
189 | $message = 'Zip archive inconsistent.';
190 | break;
191 |
192 | case ZipArchive::ER_INVAL:
193 | $message = 'Invalid argument.';
194 | break;
195 |
196 | case ZipArchive::ER_MEMORY:
197 | $message = 'Malloc failure.';
198 | break;
199 |
200 | case ZipArchive::ER_NOENT:
201 | $message = 'No such file.';
202 | break;
203 |
204 | case ZipArchive::ER_NOZIP:
205 | $message = 'Not a zip archive.';
206 | break;
207 |
208 | case ZipArchive::ER_OPEN:
209 | $message = 'Can\'t open file.';
210 | break;
211 |
212 | case ZipArchive::ER_READ:
213 | $message = 'Read error.';
214 | break;
215 |
216 | case ZipArchive::ER_SEEK:
217 | $message = 'Seek error.';
218 | break;
219 |
220 | }
221 | throw new BuildException("ZipArchive::open() failed: " . $message);
222 | }
223 |
224 | foreach ($this->filesets as $fs)
225 | {
226 | $files = $fs->getFiles($this->project, $this->includeEmpty);
227 | $fsBasedir = (null != $this->baseDir) ? $this->baseDir : $fs->getDir($this->project);
228 | $removeDir = str_replace('\\', '/', $fsBasedir->getPath());
229 |
230 | $filesToZip = [];
231 |
232 | foreach ($files as $file)
233 | {
234 | $f = new File($fsBasedir, $file);
235 |
236 | $fileAbsolutePath = $f->getPath();
237 |
238 | $fileDir = rtrim(dirname($fileAbsolutePath), '/\\');
239 | $fileBase = basename($fileAbsolutePath);
240 |
241 | // Only use lowercase for $disallowedBases because we'll convert $fileBase to lowercase
242 | $disallowedBases = ['.ds_store', '.svn', '.gitignore', 'thumbs.db'];
243 | $fileBaseLower = strtolower($fileBase);
244 |
245 | if (in_array($fileBaseLower, $disallowedBases))
246 | {
247 | continue;
248 | }
249 |
250 | if (substr($fileDir, -4) == '.svn')
251 | {
252 | continue;
253 | }
254 |
255 | if (substr(rtrim($fileAbsolutePath, '/\\'), -4) == '.svn')
256 | {
257 | continue;
258 | }
259 |
260 | $fileRelativePath = str_replace('\\', '/', $fileAbsolutePath);
261 |
262 | if (substr($fileRelativePath, 0, strlen($removeDir)) === $removeDir)
263 | {
264 | $fileRelativePath = substr($fileRelativePath, strlen($removeDir) + 1);
265 | }
266 |
267 | $fileRelativePath = empty($this->prefix) ? $fileRelativePath
268 | : ($this->prefix . '/' . $fileRelativePath);
269 |
270 | if (!file_exists($fileAbsolutePath) || !is_readable($fileAbsolutePath))
271 | {
272 | continue;
273 | }
274 |
275 | if (is_dir($fileAbsolutePath))
276 | {
277 | $zip->addEmptyDir($fileRelativePath);
278 | }
279 | elseif (PHP_OS_FAMILY === 'Windows' && is_link($fileAbsolutePath))
280 | {
281 | $zip->addFromString($fileRelativePath, file_get_contents($fileAbsolutePath));
282 | }
283 | else
284 | {
285 | $zip->addFile($fileAbsolutePath, $fileRelativePath);
286 | //$zip->addFile($fileAbsolutePath, $fileRelativePath, 0, 0, ZipArchive::FL_ENC_UTF_8);
287 | // Try to change the compression mode of every file to DEFLATE (max compatiblity)
288 | $zip->setCompressionName($fileRelativePath, ZipArchive::CM_DEFLATE);
289 | }
290 | }
291 | }
292 | }
293 | catch (IOException $ioe)
294 | {
295 | $msg = "Problem creating ZIP: " . $ioe->getMessage();
296 | $this->filesets = $savedFileSets;
297 |
298 | throw new BuildException($msg, $ioe, $this->getLocation());
299 | }
300 | finally
301 | {
302 | $zip->close();
303 | }
304 |
305 | $this->filesets = $savedFileSets;
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/phing/tasks/library/GitHubTask.php:
--------------------------------------------------------------------------------
1 | organization = $organization;
82 | }
83 |
84 | /**
85 | * Set the repository's name
86 | *
87 | * @param string $repository
88 | *
89 | * @return void
90 | */
91 | public function setRepository($repository)
92 | {
93 | $this->repository = $repository;
94 | }
95 |
96 | /**
97 | * Set the GitHub token
98 | *
99 | * @param string $token
100 | *
101 | * @return void
102 | */
103 | public function setToken($token)
104 | {
105 | $this->token = $token;
106 | }
107 |
108 | public function init()
109 | {
110 | // Create the API client object. Follow me...
111 |
112 | /**
113 | * We need a Guzzle HTTP client which is explicitly told where the hell to look for the cacert.pem file because
114 | * if curl.cainfo is not set in php.ini (you can't ini_set() this!) and you're on Windows it will consistently
115 | * fail.
116 | */
117 | $guzzleClient = new \GuzzleHttp\Client([
118 | 'verify' => \Composer\CaBundle\CaBundle::getBundledCaBundlePath() ,
119 | ]);
120 |
121 | // Then we need to create an HTTPlug client adapter to the Guzzle client
122 | $guzzleAdapter = new Http\Adapter\Guzzle6\Client($guzzleClient);
123 | // In turn, we need to make an HTTPBuilder object to that adapter
124 | $httpBuilder = new Github\HttpClient\Builder($guzzleAdapter);
125 | // Finally we have our client.
126 | $this->client = new Client($httpBuilder);
127 | }
128 |
129 | public function main()
130 | {
131 | // Make sure we have a token and apply authentication
132 | if (empty($this->token))
133 | {
134 | throw new RuntimeException('You need to provide your GitHub token.');
135 | }
136 |
137 | $this->client->authenticate($this->token, null, Client::AUTH_ACCESS_TOKEN);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/phing/tasks/library/JPAFileSet.php:
--------------------------------------------------------------------------------
1 | files !== null)
35 | {
36 | return $this->files;
37 | }
38 |
39 | $p = $this->getProject();
40 |
41 | $ds = $this->getDirectoryScanner($p);
42 | $this->files = $ds->getIncludedFiles();
43 |
44 | if ($includeEmpty)
45 | {
46 | // first any empty directories that will not be implicitly added by any of the files
47 | $implicitDirs = array();
48 |
49 | foreach ($this->files as $file)
50 | {
51 | $implicitDirs[] = dirname($file);
52 | }
53 |
54 | $incDirs = $ds->getIncludedDirectories();
55 |
56 | /**
57 | * We'll need to add to that list of implicit dirs any directories that contain other *directories* (and
58 | * not files), since otherwise we get duplicate directories in the resulting JPA archive.
59 | */
60 | foreach ($incDirs as $dir)
61 | {
62 | foreach ($incDirs as $dircheck)
63 | {
64 | if (!empty($dir) && $dir == dirname($dircheck))
65 | {
66 | $implicitDirs[] = $dir;
67 | }
68 | }
69 | }
70 |
71 | $implicitDirs = array_unique($implicitDirs);
72 |
73 | // Now add any empty dirs (dirs not covered by the implicit dirs) to the files array.
74 | foreach ($incDirs as $dir)
75 | {
76 | // We cannot simply use array_diff() since we want to disregard empty/. dirs
77 |
78 | if ($dir != "" && $dir != "." && !in_array($dir, $implicitDirs))
79 | {
80 | // It's an empty dir, so we'll add it.
81 | $this->files[] = $dir;
82 | }
83 | }
84 | }
85 |
86 | return $this->files;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/phing/tasks/library/ZipmeFileSet.php:
--------------------------------------------------------------------------------
1 | setExpandSymbolicLinks(true);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phing/tasks/library/jpa.php:
--------------------------------------------------------------------------------
1 | dataFileName = $archive;
72 |
73 | // Try to kill the archive if it exists
74 | if (file_exists($this->dataFileName))
75 | {
76 | @unlink($this->dataFileName);
77 | }
78 |
79 | @touch($this->dataFileName);
80 |
81 | $fp = @fopen($this->dataFileName, "wb");
82 |
83 | if ($fp !== false)
84 | {
85 | @ftruncate($fp, 0);
86 | @fclose($fp);
87 | }
88 |
89 | if (!is_writable($archive))
90 | {
91 | throw new RuntimeException("Can't open $archive for writing");
92 | }
93 |
94 | // Write the initial instance of the archive header
95 | $this->writeArchiveHeader();
96 | }
97 |
98 | /**
99 | * Adds a file to the archive
100 | *
101 | * @param string $from The full pathname to the file you want to add to the archive
102 | * @param string $to [optional] The relative pathname to store in the archive
103 | *
104 | * @return void
105 | */
106 | public function addFile($from, $to = null)
107 | {
108 | // Skip the following files: .gitignore, .DS_Store, Thumbs.db
109 | $basename = strtolower(basename($from));
110 |
111 | if (in_array($basename, array('.gitignore', '.ds_store', 'thumbs.db')))
112 | {
113 | return;
114 | }
115 |
116 | // See if it's a directory
117 | $isDir = is_dir($from);
118 |
119 | // Get real size before compression
120 | $fileSize = $isDir ? 0 : filesize($from);
121 |
122 | // Set the compression method
123 | $compressionMethod = function_exists('gzcompress') ? 1 : 0;
124 |
125 | // Decide if we will compress
126 | if ($isDir)
127 | {
128 | $compressionMethod = 0; // don't compress directories...
129 | }
130 |
131 | $storedName = empty($to) ? $from : $to;
132 | $storedName = self::TranslateWinPath($storedName);
133 |
134 | /* "Entity Description Block" segment. */
135 | // File size
136 | $unc_len = $fileSize;
137 | $c_len = $unc_len;
138 | $storedName .= ($isDir) ? "/" : "";
139 |
140 | if ($compressionMethod == 1)
141 | {
142 | $udata = @file_get_contents($from);
143 |
144 | if ($udata === false)
145 | {
146 | throw new RuntimeException("File $from is unreadable");
147 | }
148 |
149 | // Proceed with compression
150 | $zdata = @gzcompress($udata, 9);
151 |
152 | if ($zdata === false)
153 | {
154 | // If compression fails, let it behave like no compression was available
155 | $compressionMethod = 0;
156 | }
157 | else
158 | {
159 | unset($udata);
160 | $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
161 | $c_len = strlen($zdata);
162 | }
163 |
164 | }
165 |
166 | $this->compressedSize += $c_len;
167 | $this->uncompressedSize += $fileSize;
168 | $this->fileCount++;
169 |
170 | // Get file permissions
171 | $perms = @fileperms($from);
172 |
173 | // Calculate Entity Description Block length
174 | $blockLength = 21 + strlen($storedName);
175 |
176 | // Open data file for output
177 | $fp = @fopen($this->dataFileName, "ab");
178 |
179 | if ($fp === false)
180 | {
181 | throw new RuntimeException("Could not open archive file '{$this->dataFileName}' for append!");
182 | }
183 |
184 | // Entity Description Block header
185 | $this->writeToArchive($fp, self::entityHeaderSignature);
186 | // Entity Description Block header length
187 | $this->writeToArchive($fp, pack('v', $blockLength));
188 | // Length of entity path
189 | $this->writeToArchive($fp, pack('v', strlen($storedName)));
190 | // Entity path
191 | $this->writeToArchive($fp, $storedName);
192 | // Entity type
193 | $this->writeToArchive($fp, pack('C', ($isDir ? 0 : 1)));
194 | // Compression method
195 | $this->writeToArchive($fp, pack('C', $compressionMethod));
196 | // Compressed size
197 | $this->writeToArchive($fp, pack('V', $c_len));
198 | // Uncompressed size
199 | $this->writeToArchive($fp, pack('V', $unc_len));
200 | // Entity permissions
201 | $this->writeToArchive($fp, pack('V', $perms));
202 |
203 | /* "File data" segment. */
204 | if ($compressionMethod == 1)
205 | {
206 | // Just dump the compressed data
207 | $this->writeToArchive($fp, $zdata);
208 |
209 | unset($zdata);
210 | fclose($fp);
211 |
212 | return;
213 | }
214 |
215 | if (!$isDir)
216 | {
217 | // Copy the file contents, ignore directories
218 | $zdatafp = @fopen($from, "rb");
219 |
220 | while (!feof($zdatafp))
221 | {
222 | $zdata = fread($zdatafp, 524288);
223 | $this->writeToArchive($fp, $zdata);
224 | }
225 |
226 | fclose($zdatafp);
227 | }
228 |
229 | fclose($fp);
230 | }
231 |
232 | /**
233 | * Updates the Standard Header with current information
234 | *
235 | * @return void
236 | */
237 | public function finalize()
238 | {
239 | $this->writeArchiveHeader();
240 | }
241 |
242 | /**
243 | * Outputs a Standard Header at the top of the file
244 | *
245 | * @return void
246 | */
247 | private function writeArchiveHeader()
248 | {
249 | $fp = @fopen($this->dataFileName, 'r+');
250 |
251 | if ($fp === false)
252 | {
253 | throw new RuntimeException('Could not open ' . $this->dataFileName . ' for writing. Check permissions and open_basedir restrictions.');
254 | }
255 |
256 | // ID string (JPA)
257 | $this->writeToArchive($fp, self::standardHeaderSignature);
258 | // Header length; fixed to 19 bytes
259 | $this->writeToArchive($fp, pack('v', 19));
260 | // Major version
261 | $this->writeToArchive($fp, pack('C', _JPA_MAJOR));
262 | // Minor version
263 | $this->writeToArchive($fp, pack('C', _JPA_MINOR));
264 | // File count
265 | $this->writeToArchive($fp, pack('V', $this->fileCount));
266 | // Size of files when extracted
267 | $this->writeToArchive($fp, pack('V', $this->uncompressedSize));
268 | // Size of files when stored
269 | $this->writeToArchive($fp, pack('V', $this->compressedSize));
270 |
271 | @fclose($fp);
272 | }
273 |
274 | /**
275 | * Pure binary write to file
276 | *
277 | * @param resource $fp Handle to a file
278 | * @param string $data The data to write to the file
279 | *
280 | * @return void
281 | */
282 | private function writeToArchive($fp, $data)
283 | {
284 | $len = strlen($data);
285 | $ret = fwrite($fp, $data, $len);
286 |
287 | if ($ret === false)
288 | {
289 | throw new RuntimeException("Can't write to archive");
290 | }
291 | }
292 |
293 | // Convert Windows paths to UNIX
294 | private static function TranslateWinPath($p_path)
295 | {
296 | if (stristr(php_uname(), 'windows'))
297 | {
298 | // Change potential windows directory separator
299 | if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\'))
300 | {
301 | $p_path = strtr($p_path, '\\', '/');
302 | }
303 | }
304 |
305 | return $p_path;
306 | }
307 |
308 | /**
309 | * Get the list of ignored files
310 | *
311 | * @return array
312 | */
313 | public function getIgnoredFiles(): array
314 | {
315 | return $this->ignoredFiles;
316 | }
317 |
318 | /**
319 | * Set the list of ignored files
320 | *
321 | * @param array $ignoredFiles
322 | */
323 | public function setIgnoredFiles(array $ignoredFiles)
324 | {
325 | $this->ignoredFiles = $ignoredFiles;
326 | }
327 |
328 | /**
329 | * Add to the list of ignored files
330 | *
331 | * @param array $ignoredFiles
332 | */
333 | public function addIgnoredFiles(array $ignoredFiles)
334 | {
335 | $this->ignoredFiles = array_merge($ignoredFiles, $this->ignoredFiles);
336 | $this->ignoredFiles = array_unique($this->ignoredFiles);
337 | }
338 |
339 | }
340 |
--------------------------------------------------------------------------------
/src/Composer/InstallationScript.php:
--------------------------------------------------------------------------------
1 | open($zipFile);
36 | $zip->extractTo($targetDir);
37 | $zip->close();
38 |
39 | $di = new DirectoryIterator($targetDir);
40 |
41 | /** @var DirectoryIterator $item */
42 | foreach ($di as $item)
43 | {
44 | if (!$item->isDir() || $item->isDot())
45 | {
46 | continue;
47 | }
48 |
49 | self::moveDir($item->getPathname(), $targetDir);
50 |
51 | break;
52 | }
53 | }
54 |
55 | private static function downloadDbXslSource(Event $event): string
56 | {
57 | $workDir = realpath(__DIR__ . '/../..');
58 | $extras = $event->getComposer()->getPackage()->getExtra();
59 | $source = $extras['dbxsl']['source'];
60 | $baseName = basename($source);
61 |
62 | $target = $workDir . '/cache/' . $baseName;
63 |
64 | if (is_file($target))
65 | {
66 | return $target;
67 | }
68 |
69 | file_put_contents($target, file_get_contents($source));
70 |
71 | return $target;
72 | }
73 |
74 | private static function moveDir(string $sourceDir, string $targetDir): void
75 | {
76 | $di = new DirectoryIterator($sourceDir);
77 |
78 | /** @var DirectoryIterator $item */
79 | foreach ($di as $item)
80 | {
81 | if ($item->isDot())
82 | {
83 | continue;
84 | }
85 |
86 | if ($item->isFile() || $item->isLink())
87 | {
88 | @rename($item->getPathname(), $targetDir . '/' . $item->getBasename());
89 |
90 | continue;
91 | }
92 |
93 | mkdir($targetDir . '/' . $item->getBasename(), 0755, true);
94 |
95 | self::moveDir($item->getPathname(), $targetDir . '/' . $item->getBasename());
96 | }
97 |
98 | rmdir($sourceDir);
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/src/LinkLib/MapResult.php:
--------------------------------------------------------------------------------
1 | symlinkPath.
14 | * @property array $files The symlink files map. Each entry is in the format realPath => symlinkPath.
15 | * @property array $hardfiles The hard link files map. Each entry is in the format realPath => linkPath.
16 | */
17 | class MapResult
18 | {
19 | private $symlinkFiles = [];
20 |
21 | private $hardLinkFiles = [];
22 |
23 | private $symlinkFolders = [];
24 |
25 | public function __get(string $name)
26 | {
27 | switch ($name)
28 | {
29 | case 'dirs':
30 | return $this->getSymlinkFolders();
31 | break;
32 |
33 | case 'files':
34 | return $this->getSymlinkFiles();
35 | break;
36 |
37 | case 'hardfiles':
38 | return $this->getHardLinkFiles();
39 | break;
40 | }
41 |
42 | return null;
43 | }
44 |
45 | public function __set(string $name, $value)
46 | {
47 | switch ($name)
48 | {
49 | case 'dirs':
50 | $this->setSymlinkFolders($value);
51 | break;
52 |
53 | case 'files':
54 | $this->setSymlinkFiles($value);
55 | break;
56 |
57 | case 'hardfiles':
58 | $this->setHardLinkFiles($value);
59 | break;
60 | }
61 | }
62 |
63 | /**
64 | * @return array
65 | */
66 | public function getSymlinkFiles()
67 | {
68 | return $this->symlinkFiles;
69 | }
70 |
71 | /**
72 | * @param array $symlinkFiles
73 | */
74 | public function setSymlinkFiles(array $symlinkFiles)
75 | {
76 | $this->symlinkFiles = $symlinkFiles;
77 | }
78 |
79 | /**
80 | * @return array
81 | */
82 | public function getHardLinkFiles()
83 | {
84 | return $this->hardLinkFiles;
85 | }
86 |
87 | /**
88 | * @param array $hardLinkFiles
89 | */
90 | public function setHardLinkFiles(array $hardLinkFiles)
91 | {
92 | $this->hardLinkFiles = $hardLinkFiles;
93 | }
94 |
95 | /**
96 | * @return array
97 | */
98 | public function getSymlinkFolders()
99 | {
100 | return $this->symlinkFolders;
101 | }
102 |
103 | /**
104 | * @param array $symlinkFolders
105 | */
106 | public function setSymlinkFolders(array $symlinkFolders)
107 | {
108 | $this->symlinkFolders = $symlinkFolders;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/LinkLib/Relink.php:
--------------------------------------------------------------------------------
1 | repositoryRoot = $repositoryRoot;
67 | $this->extensions = [];
68 |
69 | // Detect extensions
70 | $this->extensions = array_merge($this->extensions, Package::detect($this->repositoryRoot));
71 | $this->extensions = array_merge($this->extensions, Component::detect($this->repositoryRoot));
72 | $this->extensions = array_merge($this->extensions, Library::detect($this->repositoryRoot));
73 | $this->extensions = array_merge($this->extensions, Module::detect($this->repositoryRoot));
74 | $this->extensions = array_merge($this->extensions, Plugin::detect($this->repositoryRoot));
75 | $this->extensions = array_merge($this->extensions, Template::detect($this->repositoryRoot));
76 | $this->extensions = array_merge($this->extensions, File::detect($this->repositoryRoot));
77 | }
78 |
79 | /**
80 | * Unlink all detected extensions from the given site
81 | *
82 | * @param string $siteRoot The absolute path to the site's root
83 | */
84 | public function unlink($siteRoot)
85 | {
86 | foreach ($this->extensions as $extension)
87 | {
88 | if ($this->verbose)
89 | {
90 | $extensionTag = $extension->getKeyName();
91 | echo "UNLINK $extensionTag\n";
92 | }
93 |
94 | $extension->setSiteRoot($siteRoot);
95 | $extension->unlink();
96 | }
97 | }
98 |
99 | /**
100 | * Relink all detected extensions to the given site
101 | *
102 | * @param string $siteRoot The absolute path to the site's root
103 | */
104 | public function relink($siteRoot)
105 | {
106 | /** @var AbstractScanner $extension */
107 | foreach ($this->extensions as $extension)
108 | {
109 | if ($this->verbose)
110 | {
111 | $extensionTag = $extension->getKeyName();
112 | echo "RELINK $extensionTag\n";
113 | }
114 |
115 | $extension->setSiteRoot($siteRoot);
116 | $extension->setVerbose($this->dryRun && $this->verbose);
117 | $extension->setVerbose($this->verbose);
118 | $extension->setDryRun($this->dryRun);
119 | $extension->relink();
120 | }
121 | }
122 |
123 | /**
124 | * Set the verbosity flag
125 | *
126 | * @param bool $verbose
127 | */
128 | public function setVerbose(bool $verbose)
129 | {
130 | $this->verbose = $verbose;
131 | }
132 |
133 | /**
134 | * Set the Dry Run mode flag
135 | *
136 | * @param bool $dryRun
137 | */
138 | public function setDryRun(bool $dryRun): void
139 | {
140 | $this->dryRun = $dryRun;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/LinkLib/ScanResult.php:
--------------------------------------------------------------------------------
1 |
25 | * Components: com_something
26 | * Modules: foobar (NOT mod_foobar!)
27 | * Plugins: foobar (NOT plg_folder_foobar!)
28 | * Templates: foobar (NOT tpl_foobar!)
29 | * Libraries: foobar (NOT lib_foobar!)
30 | *
31 | *
32 | * @var string
33 | */
34 | public $extension = '';
35 |
36 | /**
37 | * If the extension is a plugin this is the plugin's folder, e.g. system, content, user, ...
38 | *
39 | * @var string
40 | */
41 | public $pluginFolder = '';
42 |
43 | /**
44 | * Absolute path to the folder containing the front-end files for the extension
45 | *
46 | * @var string
47 | */
48 | public $siteFolder = '';
49 |
50 | /**
51 | * Absolute path to the folder containing the back-end files for the extension
52 | *
53 | * @var string
54 | */
55 | public $adminFolder = '';
56 |
57 | /**
58 | * Absolute path to the folder containing the Joomla 4 API application files for the extension
59 | *
60 | * @var string
61 | */
62 | public $apiFolder = '';
63 |
64 | /**
65 | * Absolute path to the folder containing the library files for the extensions (they go into /libraries)
66 | *
67 | * @var string
68 | */
69 | public $libraryFolder = '';
70 |
71 | /**
72 | * Absolute path to the folder containing the media files for the extension
73 | *
74 | * @var string
75 | */
76 | public $mediaFolder = '';
77 |
78 | /**
79 | * Name of the destination subdirectory of the media folder on the site.
80 | *
81 | * For example, if this is "foobar" then the media files will be stored on the site's "media/foobar" directory.
82 | *
83 | * @var string
84 | */
85 | public $mediaDestination = '';
86 |
87 | /**
88 | * Absolute path to the folder containing the CLI files for the extension
89 | *
90 | * @var string
91 | */
92 | public $cliFolder = '';
93 |
94 | /**
95 | * Absolute path to the folder containing the front-end language files for the extension. Note: this is the folder
96 | * containing the actual language directories such as en-GB, de-DE, fr-FR and so on.
97 | *
98 | * @var string
99 | */
100 | public $siteLangPath = '';
101 |
102 | /**
103 | * List of the absolute paths of all of the front-end language files, listed by language.
104 | *
105 | * For example:
106 | * [
107 | * 'en-GB' => [
108 | * '/path/to/en-GB/en-GB.com_foobar.ini',
109 | * '/path/to/en-GB/en-GB.com_foobar.sys.ini',
110 | * ],
111 | * 'fr-FR' => [
112 | * '/path/to/fr-FR/fr-FR.com_foobar.ini',
113 | * '/path/to/fr-FR/fr-FR.com_foobar.sys.ini',
114 | * ]
115 | * ]
116 | *
117 | * @var array
118 | */
119 | public $siteLangFiles = [];
120 |
121 | /**
122 | * Absolute path to the folder containing the back-end language files for the extension. Note: this is the folder
123 | * containing the actual language directories such as en-GB, de-DE, fr-FR and so on.
124 | *
125 | * @var string
126 | */
127 | public $adminLangPath = '';
128 |
129 | /**
130 | * List of the absolute paths of all of the back-end language files, listed by language.
131 | *
132 | * For example:
133 | * [
134 | * 'en-GB' => [
135 | * '/path/to/en-GB/en-GB.com_foobar.ini',
136 | * '/path/to/en-GB/en-GB.com_foobar.sys.ini',
137 | * ],
138 | * 'fr-FR' => [
139 | * '/path/to/fr-FR/fr-FR.com_foobar.ini',
140 | * '/path/to/fr-FR/fr-FR.com_foobar.sys.ini',
141 | * ]
142 | * ]
143 | *
144 | * @var array
145 | */
146 | public $adminLangFiles = [];
147 |
148 | /**
149 | * Absolute path to the folder containing the API language files for the extension. Note: this is the folder
150 | * containing the actual language directories such as en-GB, de-DE, fr-FR and so on.
151 | *
152 | * @var string
153 | */
154 | public $apiLangPath = '';
155 |
156 | /**
157 | * List of the absolute paths of all of the API language files, listed by language.
158 | *
159 | * For example:
160 | * [
161 | * 'en-GB' => [
162 | * '/path/to/en-GB/en-GB.com_foobar.ini',
163 | * '/path/to/en-GB/en-GB.com_foobar.sys.ini',
164 | * ],
165 | * 'fr-FR' => [
166 | * '/path/to/fr-FR/fr-FR.com_foobar.ini',
167 | * '/path/to/fr-FR/fr-FR.com_foobar.sys.ini',
168 | * ]
169 | * ]
170 | *
171 | * @var array
172 | */
173 | public $apiLangFiles = [];
174 |
175 | /**
176 | * The name of the script file of the extension.
177 | *
178 | * Only applies to extension types where the script is outside the main extension directory, i.e. file, package and
179 | * library extensions.
180 | *
181 | * @var string
182 | */
183 | public $scriptFileName = '';
184 |
185 | /**
186 | * List of the filesets of a Joomla file extension, keyed by target directory (relative path).
187 | *
188 | * For example, the following manifest:
189 | * ```xml
190 | *
191 | *
192 | * foo.php
193 | *
194 | *
195 | * bar.php
196 | *
197 | *
198 | * ```
199 | * results in the array:
200 | * ```php
201 | * [
202 | * 'cli' => [
203 | * 'foo.php'
204 | * ],
205 | * 'media/foobar' => [
206 | * 'bar.php'
207 | * ]
208 | * ]
209 | * ```
210 | *
211 | * @var array
212 | */
213 | public $fileSets = [];
214 |
215 | /**
216 | * Same as $fileSets but for the tags nested inside the tags.
217 | *
218 | * @var array
219 | */
220 | public $folderSets = [];
221 |
222 | /**
223 | * Return the name of the extension as it'd be reported by Joomla
224 | *
225 | * @param bool $includeSiteAdmin If enabled, modules and templates will have a site_ or admin_ indicator before
226 | * the name to indicate if they are meant to be installed in front- or backend of
227 | * the site respectively. This is non-canonical to how Joomla! extensions are
228 | * typically named.
229 | *
230 | * @return string;
231 | */
232 | public function getJoomlaExtensionName($includeSiteAdmin = false)
233 | {
234 | $prefix = '';
235 |
236 | switch ($this->extensionType)
237 | {
238 | case 'module':
239 | if ($includeSiteAdmin)
240 | {
241 | [$prefix, $extension] = explode('_', $this->extension);
242 |
243 | if (!empty($this->siteFolder))
244 | {
245 | $prefix .= '_site_';
246 | }
247 | else
248 | {
249 | $prefix .= '_admin_';
250 | }
251 |
252 | return $prefix . $extension;
253 | }
254 |
255 | break;
256 |
257 | case 'plugin':
258 | $prefix = 'plg_' . $this->pluginFolder . '_';
259 | break;
260 |
261 | case 'template':
262 | $prefix = 'tpl_';
263 |
264 | if ($includeSiteAdmin)
265 | {
266 | if (!empty($this->siteFolder))
267 | {
268 | $prefix .= 'site_';
269 | }
270 | else
271 | {
272 | $prefix .= 'admin_';
273 | }
274 | }
275 |
276 | break;
277 |
278 | case 'library':
279 | $prefix = 'lib_';
280 | break;
281 |
282 | case 'package':
283 | $prefix = 'pkg_';
284 | break;
285 |
286 | case 'files':
287 | case 'file':
288 | $prefix = '';
289 | break;
290 | }
291 |
292 | return $prefix . $this->extension;
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/src/LinkLib/Scanner/File.php:
--------------------------------------------------------------------------------
1 | manifestExtensionType = 'file';
32 |
33 | parent::__construct($extensionRoot, $languageRoot);
34 | }
35 |
36 | /**
37 | * Detect extensions of type File in the repository and return an array of ScannerInterface objects for them.
38 | *
39 | * @param string $repositoryRoot The repository root to scan
40 | *
41 | * @return ScannerInterface[]
42 | */
43 | public static function detect(string $repositoryRoot): array
44 | {
45 | $possiblePaths = [
46 | $repositoryRoot . '/component/cli',
47 | $repositoryRoot . '/fof',
48 | $repositoryRoot . '/joomla',
49 | ];
50 |
51 | $rootPath = $repositoryRoot . '/files';
52 | $extensions = [];
53 |
54 | if (is_dir($rootPath))
55 | {
56 | $di = new \DirectoryIterator($rootPath);
57 |
58 | foreach ($di as $folder)
59 | {
60 | if ($folder->isDot() || !$folder->isDir())
61 | {
62 | continue;
63 | }
64 |
65 | $extName = $folder->getFilename();
66 |
67 | // Figure out the language root to use
68 | $languageRoot = null;
69 | $translationsRoot = self::getTranslationsRoot($repositoryRoot);
70 |
71 | if ($translationsRoot)
72 | {
73 | $languageRoot = $translationsRoot . '/file/' . $extName;
74 |
75 | if (!is_dir($languageRoot))
76 | {
77 | $languageRoot = null;
78 | }
79 | }
80 |
81 | // Get the extension ScannerInterface object
82 | $possiblePaths[] = $folder->getRealPath();
83 | }
84 | }
85 |
86 | foreach ($possiblePaths as $path)
87 | {
88 | if (!file_exists($path) || !is_dir($path))
89 | {
90 | continue;
91 | }
92 |
93 | $extName = basename($path);
94 |
95 | // Figure out the language root to use
96 | $languageRoot = null;
97 | $translationsRoot = self::getTranslationsRoot($repositoryRoot);
98 |
99 | if ($translationsRoot)
100 | {
101 | $languageRoot = $translationsRoot . '/file/' . $extName;
102 |
103 | if (!is_dir($languageRoot))
104 | {
105 | $languageRoot = null;
106 | }
107 | }
108 |
109 | $extension = new File($path, $languageRoot);
110 | $extensions[] = $extension;
111 | }
112 |
113 | return $extensions;
114 | }
115 |
116 | /**
117 | * Scans the extension for files and folders to link
118 | *
119 | * @return ScanResult
120 | */
121 | public function scan(): ScanResult
122 | {
123 | // Get the XML manifest
124 | $xmlDoc = $this->getXMLManifest();
125 |
126 | if (empty($xmlDoc))
127 | {
128 | throw new RuntimeException("Cannot get XML manifest for file extension in {$this->extensionRoot}");
129 | }
130 |
131 | // Initialize the result
132 | $result = new ScanResult();
133 | $result->extensionType = 'file';
134 |
135 | // Get the extension name
136 | $fileExtension = strtolower($xmlDoc->getElementsByTagName('name')->item(0)->nodeValue);
137 |
138 | if (is_null($fileExtension))
139 | {
140 | throw new RuntimeException("Cannot find the file extension name in the XML manifest for {$this->extensionRoot}");
141 | }
142 |
143 | $result->extension = $fileExtension;
144 |
145 | // Get the extension's tags nested inside tags
146 | $xpathOuter = new \DOMXPath($xmlDoc);
147 |
148 | /** @var \DOMElement $filesNode */
149 | foreach ($xpathOuter->query('/extension/fileset/files') as $filesNode)
150 | {
151 | if (!$filesNode->hasChildNodes() || !$filesNode->hasAttribute('target'))
152 | {
153 | continue;
154 | }
155 |
156 | $folder = '';
157 |
158 | if ($filesNode->hasAttribute('folder'))
159 | {
160 | $folder = (string) $filesNode->getAttribute('folder');
161 | }
162 |
163 | $target = $filesNode->getAttribute('target');
164 | $result->fileSets[$target] = $result->fileSets[$target] ?? [];
165 | $result->folderSets[$target] = $result->folderSets[$target] ?? [];
166 |
167 | if ($folder)
168 | {
169 | $result->folderSets[$target][] = $folder;
170 | }
171 | else
172 | {
173 | /** @var \DOMNode $fileNode */
174 | foreach ($filesNode->getElementsByTagName('file') as $fileNode)
175 | {
176 | $result->fileSets[$target][] = $fileNode->textContent;
177 | }
178 |
179 | /** @var \DOMNode $fileNode */
180 | foreach ($filesNode->getElementsByTagName('folder') as $fileNode)
181 | {
182 | $result->folderSets[$target][] = $fileNode->textContent;
183 | }
184 | }
185 |
186 | if (empty($result->fileSets[$target]))
187 | {
188 | unset($result->fileSets[$target]);
189 | }
190 |
191 | if (empty($result->folderSets[$target]))
192 | {
193 | unset($result->folderSets[$target]);
194 | }
195 | }
196 |
197 | return $result;
198 | }
199 |
200 | /**
201 | * Parses the last scan and generates a link map
202 | *
203 | * @return MapResult
204 | */
205 | public function map(): MapResult
206 | {
207 | $scan = $this->getScanResults();
208 | $result = parent::map();
209 |
210 | // Map the package files
211 | $files = [];
212 |
213 | foreach ($scan->fileSets as $relativeTarget => $sourceFiles)
214 | {
215 | $absoluteTarget = $this->siteRoot . '/' . trim($relativeTarget, '/');
216 |
217 | foreach ($sourceFiles as $sourceFile)
218 | {
219 | $basename = basename($sourceFile);
220 | $files[$absoluteTarget . '/' . $basename] = $this->extensionRoot . '/' . $sourceFile;
221 | }
222 | }
223 |
224 | $result->hardfiles = array_merge($result->hardfiles, $files);
225 |
226 | // Map the package folders
227 | $folders = [];
228 |
229 | foreach ($scan->folderSets as $relativeTarget => $sourceFolders)
230 | {
231 | $absoluteTarget = $this->siteRoot . '/' . trim($relativeTarget, '/');
232 |
233 | foreach ($sourceFolders as $sourceFolder)
234 | {
235 | $folders[$this->extensionRoot . '/' . $sourceFolder] = $absoluteTarget;
236 | }
237 | }
238 |
239 | $result->dirs = array_merge($result->dirs, $folders);
240 |
241 | // Map XML manifest
242 | if (!empty($this->xmlManifestPath))
243 | {
244 | $result->files = array_merge($result->files, [
245 | $this->xmlManifestPath => $this->siteRoot . '/administrator/manifests/files/' . basename($this->xmlManifestPath),
246 | ]);
247 | }
248 |
249 | // Map script file
250 | if ($scriptPath = $this->getScriptAbsolutePath($scan))
251 | {
252 | $result->files = array_merge($result->files, [
253 | $scriptPath => $this->siteRoot . '/administrator/manifests/files/' . $scan->extensionType . '/' . basename($scriptPath),
254 | ]);
255 | }
256 |
257 | return $result;
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/LinkLib/Scanner/Library.php:
--------------------------------------------------------------------------------
1 | manifestExtensionType = 'library';
32 |
33 | parent::__construct($extensionRoot, $languageRoot);
34 | }
35 |
36 | /**
37 | * Scans the extension for files and folders to link
38 | *
39 | * @return ScanResult
40 | */
41 | public function scan(): ScanResult
42 | {
43 | // Get the XML manifest
44 | $xmlDoc = $this->getXMLManifest();
45 |
46 | if (empty($xmlDoc))
47 | {
48 | throw new RuntimeException("Cannot get XML manifest for library in {$this->extensionRoot}");
49 | }
50 |
51 | // Initialize the result
52 | $result = new ScanResult();
53 | $result->extensionType = 'library';
54 |
55 | // Get the extension name
56 | $library = strtolower($xmlDoc->getElementsByTagName('name')->item(0)->nodeValue);
57 |
58 | if (is_null($library))
59 | {
60 | throw new RuntimeException("Cannot find the library name in the XML manifest for {$this->extensionRoot}");
61 | }
62 |
63 | $result->extension = $library;
64 |
65 | // Get the library tags
66 | $result->libraryFolder = $this->extensionRoot;
67 | $allFilesTags = $xmlDoc->getElementsByTagName('files');
68 |
69 | $nodePath0 = $allFilesTags->item(0)->getNodePath();
70 | $siteFilesTag = $allFilesTags->item(0);
71 |
72 | if ($nodePath0 != '/extension/files')
73 | {
74 | $siteFilesTag = $allFilesTags->item(1);
75 | }
76 |
77 | if ($siteFilesTag->hasAttribute('folder'))
78 | {
79 | $result->libraryFolder = $this->extensionRoot . '/' . $siteFilesTag->getAttribute('folder');
80 | }
81 |
82 | // Get the media folder
83 | $result->mediaFolder = null;
84 | $result->mediaDestination = null;
85 | $allMediaTags = $xmlDoc->getElementsByTagName('media');
86 |
87 | if ($allMediaTags->length >= 1)
88 | {
89 | $result->mediaFolder = $this->extensionRoot . '/' . (string) $allMediaTags->item(0)
90 | ->getAttribute('folder');
91 | $result->mediaDestination = $allMediaTags->item(0)->getAttribute('destination');
92 | }
93 |
94 | // Get the tags for front and back-end
95 | $xpath = new \DOMXPath($xmlDoc);
96 |
97 | // Get frontend language files from the frontend tag
98 | $result->siteLangPath = null;
99 | $result->siteLangFiles = [];
100 | $frontEndLanguageNodes = $xpath->query('/extension/languages');
101 |
102 | foreach ($frontEndLanguageNodes as $node)
103 | {
104 | list($languageRoot, $languageFiles) = $this->scanLanguageNode($node);
105 |
106 | if (!empty($languageFiles))
107 | {
108 | $result->siteLangFiles = $languageFiles;
109 | $result->siteLangPath = $languageRoot;
110 | }
111 | }
112 |
113 | // Get backend language files from the backend tag
114 | $result->adminLangPath = null;
115 | $result->adminLangFiles = [];
116 | $backEndLanguageNodes = $xpath->query('/extension/administration/languages');
117 |
118 | foreach ($backEndLanguageNodes as $node)
119 | {
120 | list($languageRoot, $languageFiles) = $this->scanLanguageNode($node);
121 |
122 | if (!empty($languageFiles))
123 | {
124 | $result->adminLangFiles = $languageFiles;
125 | $result->adminLangPath = $languageRoot;
126 | }
127 | }
128 |
129 | // Scan language files in a separate root, if one is specified
130 | if (!empty($this->languageRoot))
131 | {
132 | $langPath = $this->languageRoot . '/libraries/' . $library . '/frontend';
133 | $langFiles = $this->scanLanguageFolder($langPath);
134 |
135 | if (!empty($langFiles))
136 | {
137 | $result->siteLangPath = $langPath;
138 | $result->siteLangFiles = $langFiles;
139 | }
140 |
141 | $langPath = $this->languageRoot . '/libraries/' . $library . '/backend';
142 | $langFiles = $this->scanLanguageFolder($langPath);
143 |
144 | if (!empty($langFiles))
145 | {
146 | $result->adminLangPath = $langPath;
147 | $result->adminLangFiles = $langFiles;
148 | }
149 | }
150 |
151 | return $result;
152 | }
153 |
154 | /**
155 | * Parses the last scan and generates a link map
156 | *
157 | * @return MapResult
158 | */
159 | public function map(): MapResult
160 | {
161 | $scan = $this->getScanResults();
162 | $result = parent::map();
163 |
164 | // Map the library itself
165 | $dirs = [
166 | $scan->libraryFolder => $this->siteRoot . '/libraries/' . $scan->extension,
167 | ];
168 |
169 | $result->dirs = array_merge($result->dirs, $dirs);
170 |
171 | // Map XML manifest
172 | if (!empty($this->xmlManifestPath))
173 | {
174 | $manifestFrom = $this->extensionRoot . '/' . $this->xmlManifestPath;
175 | $manifestTo = $this->siteRoot . '/administrator/manifests/libraries/' . $this->xmlManifestPath;
176 | $result->files = array_merge($result->files, [$manifestFrom => $manifestTo]);
177 | }
178 |
179 | return $result;
180 | }
181 |
182 | /**
183 | * Detect extensions of type Library in the repository and return an array of ScannerInterface objects for them.
184 | *
185 | * @param string $repositoryRoot The repository root to scan
186 | *
187 | * @return ScannerInterface[]
188 | */
189 | public static function detect(string $repositoryRoot): array
190 | {
191 | $path = $repositoryRoot . '/libraries';
192 | $extensions = [];
193 |
194 | if (!is_dir($path))
195 | {
196 | return $extensions;
197 | }
198 |
199 | // Loop all libraries in the section
200 | $di = new \DirectoryIterator($path);
201 |
202 | foreach ($di as $folder)
203 | {
204 | if ($folder->isDot() || !$folder->isDir())
205 | {
206 | continue;
207 | }
208 |
209 | $extName = $folder->getFilename();
210 |
211 | // Figure out the language root to use
212 | $languageRoot = null;
213 | $translationsRoot = self::getTranslationsRoot($repositoryRoot);
214 |
215 | if ($translationsRoot)
216 | {
217 | $languageRoot = $translationsRoot . '/libraries/' . $extName;
218 |
219 | if (!is_dir($languageRoot))
220 | {
221 | $languageRoot = null;
222 | }
223 | }
224 |
225 | // Get the extension ScannerInterface object
226 | $extension = new Library($folder->getRealPath(), $languageRoot);
227 | $extensions[] = $extension;
228 | }
229 |
230 | return $extensions;
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/LinkLib/Scanner/Module.php:
--------------------------------------------------------------------------------
1 | manifestExtensionType = 'module';
32 |
33 | parent::__construct($extensionRoot, $languageRoot);
34 | }
35 |
36 | /**
37 | * Scans the extension for files and folders to link
38 | *
39 | * @return ScanResult
40 | */
41 | public function scan()
42 | {
43 | // Get the XML manifest
44 | $xmlDoc = $this->getXMLManifest();
45 |
46 | if (empty($xmlDoc))
47 | {
48 | throw new RuntimeException("Cannot get XML manifest for module in {$this->extensionRoot}");
49 | }
50 |
51 | // Intiialize the result
52 | $result = new ScanResult();
53 | $result->extensionType = 'module';
54 |
55 | // Get the extension name
56 | $files = $xmlDoc->getElementsByTagName('files')->item(0)->childNodes;
57 | $module = null;
58 |
59 | /** @var \DOMElement $file */
60 | foreach ($files as $file)
61 | {
62 | if ($file->hasAttributes())
63 | {
64 | $module = $file->getAttribute('module');
65 |
66 | break;
67 | }
68 | }
69 |
70 | if (is_null($module))
71 | {
72 | throw new RuntimeException("Cannot find the module name in the XML manifest for {$this->extensionRoot}");
73 | }
74 |
75 | $result->extension = $module;
76 |
77 | // Is this is a site or administrator module?
78 | $isSite = $xmlDoc->documentElement->getAttribute('client') == 'site';
79 |
80 | // Get the main folder to link
81 | if ($isSite)
82 | {
83 | $result->siteFolder = $this->extensionRoot;
84 | }
85 | else
86 | {
87 | $result->adminFolder = $this->extensionRoot;
88 | }
89 |
90 | // Get the media folder
91 | $result->mediaFolder = null;
92 | $result->mediaDestination = null;
93 | $allMediaTags = $xmlDoc->getElementsByTagName('media');
94 |
95 | if ($allMediaTags->length >= 1)
96 | {
97 | $result->mediaFolder = $this->extensionRoot . '/' . (string) $allMediaTags->item(0)
98 | ->getAttribute('folder');
99 | $result->mediaDestination = $allMediaTags->item(0)->getAttribute('destination');
100 | }
101 |
102 | // Get the tag
103 | $xpath = new \DOMXPath($xmlDoc);
104 | $languagesNodes = $xpath->query('/extension/languages');
105 |
106 | foreach ($languagesNodes as $node)
107 | {
108 | list($languageRoot, $languageFiles) = $this->scanLanguageNode($node);
109 |
110 | if (empty($languageFiles))
111 | {
112 | continue;
113 | }
114 |
115 | if ($isSite)
116 | {
117 | $result->siteLangFiles = $languageFiles;
118 | $result->siteLangPath = $languageRoot;
119 |
120 | continue;
121 | }
122 |
123 | $result->adminLangFiles = $languageFiles;
124 | $result->adminLangPath = $languageRoot;
125 | }
126 |
127 | // Scan language files in a separate root, if one is specified
128 | if (!empty($this->languageRoot))
129 | {
130 | $langPath = $this->languageRoot . '/modules/';
131 | $langPath .= $isSite ? 'site/' : 'admin/';
132 | $langPath .= substr($module, 4);
133 | $langFiles = $this->scanLanguageFolder($langPath);
134 |
135 | if (!empty($langFiles))
136 | {
137 | if ($isSite)
138 | {
139 | $result->siteLangPath = $langPath;
140 | $result->siteLangFiles = $langFiles;
141 | }
142 | else
143 | {
144 | $result->adminLangPath = $langPath;
145 | $result->adminLangFiles = $langFiles;
146 | }
147 | }
148 | }
149 |
150 | return $result;
151 | }
152 |
153 | /**
154 | * Parses the last scan and generates a link map
155 | *
156 | * @return MapResult
157 | */
158 | public function map()
159 | {
160 | $scan = $this->getScanResults();
161 | $result = parent::map();
162 |
163 | $source = $scan->siteFolder;
164 | $basePath = $this->siteRoot . '/';
165 |
166 | if (!empty($scan->adminFolder))
167 | {
168 | $basePath .= 'administrator/';
169 | $source = $scan->adminFolder;
170 | }
171 |
172 | $basePath .= 'modules/' . $scan->extension;
173 |
174 | // Frontend and backend directories
175 | $dirs = [
176 | $source => $basePath
177 | ];
178 |
179 | $result->dirs = array_merge($result->dirs, $dirs);
180 |
181 | return $result;
182 | }
183 |
184 | /**
185 | * Detect extensions of type Module in the repository and return an array of ScannerInterface objects for them.
186 | *
187 | * @param string $repositoryRoot The repository root to scan
188 | *
189 | * @return ScannerInterface[]
190 | */
191 | public static function detect($repositoryRoot): array
192 | {
193 | $path = $repositoryRoot . '/modules';
194 | $sections = ['site', 'admin'];
195 | $extensions = [];
196 |
197 | if (!is_dir($path))
198 | {
199 | return $extensions;
200 | }
201 |
202 | // Loop both sections (site and admin)
203 | foreach ($sections as $section)
204 | {
205 | $sectionPath = $path . '/' . $section;
206 |
207 | if (!is_dir($sectionPath))
208 | {
209 | continue;
210 | }
211 |
212 | // Loop all modules in the section
213 | $di = new \DirectoryIterator($sectionPath);
214 |
215 | foreach ($di as $folder)
216 | {
217 | if ($folder->isDot() || !$folder->isDir())
218 | {
219 | continue;
220 | }
221 |
222 | $extName = $folder->getFilename();
223 |
224 | // Figure out the language root to use
225 | $languageRoot = null;
226 | $translationsRoot = self::getTranslationsRoot($repositoryRoot);
227 |
228 | if ($translationsRoot)
229 | {
230 | $languageRoot = $translationsRoot . '/modules/' . $section . '/' . $extName;
231 |
232 | if (!is_dir($languageRoot))
233 | {
234 | $languageRoot = null;
235 | }
236 | }
237 |
238 | // Get the extension ScannerInterface object
239 | $extension = new Module($folder->getRealPath(), $languageRoot);
240 | $extensions[] = $extension;
241 | }
242 | }
243 |
244 | return $extensions;
245 | }
246 |
247 | }
248 |
--------------------------------------------------------------------------------
/src/LinkLib/Scanner/Package.php:
--------------------------------------------------------------------------------
1 | manifestExtensionType = 'package';
35 |
36 | if (!empty($forcedXmlFile))
37 | {
38 | $this->forcedXmlFile = $forcedXmlFile;
39 | }
40 |
41 | parent::__construct($extensionRoot, $languageRoot);
42 | }
43 |
44 | /**
45 | * Detect extensions of type Package in the repository and return an array of ScannerInterface objects for them.
46 | *
47 | * @param string $repositoryRoot The repository root to scan
48 | *
49 | * @return ScannerInterface[]
50 | */
51 | public static function detect(string $repositoryRoot): array
52 | {
53 | $possiblePaths = [
54 | $repositoryRoot => $repositoryRoot . '/build/templates/language',
55 | $repositoryRoot . '/build/templates' => null,
56 | ];
57 |
58 | $extensions = [];
59 |
60 | foreach ($possiblePaths as $path => $languageRoot)
61 | {
62 | if (!is_dir($path))
63 | {
64 | continue;
65 | }
66 |
67 | // Loop all packages in the section
68 | $di = new \DirectoryIterator($path);
69 |
70 | /** @var \DirectoryIterator $file */
71 | foreach ($di as $file)
72 | {
73 | if ($file->isDir() || $file->getExtension() !== 'xml')
74 | {
75 | continue;
76 | }
77 |
78 | if (substr($file->getBasename('.' . $file->getExtension()), -5) === '_core')
79 | {
80 | continue;
81 | }
82 |
83 | // Is this the right kind of XML file?
84 | $xmlDoc = new \DOMDocument();
85 | $xmlDoc->load($file->getPathname(), LIBXML_NOBLANKS | LIBXML_NOCDATA | LIBXML_NOENT | LIBXML_NONET);
86 |
87 | $rootNodes = $xmlDoc->getElementsByTagname('extension');
88 |
89 | if ($rootNodes->length < 1)
90 | {
91 | unset($xmlDoc);
92 | continue;
93 | }
94 |
95 | $root = $rootNodes->item(0);
96 |
97 | if (!$root->hasAttributes())
98 | {
99 | unset($xmlDoc);
100 | continue;
101 | }
102 |
103 | if ($root->getAttribute('type') != 'package')
104 | {
105 | unset($xmlDoc);
106 | continue;
107 | }
108 |
109 | unset($xmlDoc);
110 |
111 | $basePath = $file->getPath();
112 | $forcedXmlFile = $file->getBasename();
113 |
114 | // Get the extension ScannerInterface object
115 | $extensions[] = new Package($basePath, null, $forcedXmlFile);
116 |
117 | return $extensions;
118 | }
119 | }
120 |
121 | return $extensions;
122 | }
123 |
124 | /**
125 | * Scans the extension for files and folders to link
126 | *
127 | * @return ScanResult
128 | */
129 | public function scan(): ScanResult
130 | {
131 | $xmlDoc = $this->getXMLManifest($this->forcedXmlFile);
132 |
133 | if (empty($xmlDoc))
134 | {
135 | throw new RuntimeException("Cannot get XML manifest for package in {$this->extensionRoot}");
136 | }
137 |
138 | // Initialize the result
139 | $result = new ScanResult();
140 | $result->extensionType = 'package';
141 |
142 | // Get the extension name
143 | $packageName = strtolower($xmlDoc->getElementsByTagName('name')->item(0)->nodeValue);
144 |
145 | if (empty($packageName))
146 | {
147 | throw new RuntimeException("Cannot find the package name in the XML manifest for {$this->extensionRoot}");
148 | }
149 |
150 | // Some old packages had an invalid tag. Let's figure it out based on their filename instead.
151 | if (substr($packageName, 0, 4) !== 'pkg_')
152 | {
153 | $packageName = basename($this->xmlManifestPath, '.xml');
154 |
155 | if (substr($packageName, -5) === '_core')
156 | {
157 | $packageName = substr($packageName, 0, -5);
158 | }
159 | elseif (substr($packageName, -4) === '_pro')
160 | {
161 | $packageName = substr($packageName, 0, -4);
162 | }
163 | }
164 |
165 | if (substr($packageName, 0, 4) !== 'pkg_')
166 | {
167 | throw new RuntimeException("Invalid package name “{$packageName}” in the XML manifest for {$this->extensionRoot}");
168 | }
169 |
170 | $result->extension = substr($packageName, 4);
171 |
172 | // Get ready to query the manifest
173 | $xpath = new \DOMXPath($xmlDoc);
174 |
175 | // Get the script filename
176 | $scriptNodes = $xpath->query('/extension/scriptfile');
177 |
178 | /** @var \DOMNode $node */
179 | foreach ($scriptNodes as $node)
180 | {
181 | $result->scriptFileName = $node->textContent;
182 | }
183 |
184 | // Get language files from the tag
185 | $result->adminLangPath = null;
186 | $result->adminLangFiles = [];
187 | $backEndLanguageNodes = $xpath->query('/extension/languages');
188 |
189 | foreach ($backEndLanguageNodes as $node)
190 | {
191 | [$languageRoot, $languageFiles] = $this->scanLanguageNode($node);
192 |
193 | if (!empty($languageFiles))
194 | {
195 | $result->adminLangFiles = $languageFiles;
196 | $result->adminLangPath = $languageRoot;
197 | }
198 | }
199 |
200 | // Scan language files in a separate root, if one is specified
201 | if (!empty($this->languageRoot))
202 | {
203 | $langPath = $this->languageRoot . '/packages/' . $packageName;
204 | $langFiles = $this->scanLanguageFolder($langPath);
205 |
206 | if (!empty($langFiles))
207 | {
208 | $result->adminLangPath = $langPath;
209 | $result->adminLangFiles = $langFiles;
210 | }
211 | }
212 |
213 | return $result;
214 | }
215 |
216 | /**
217 | * Parses the last scan and generates a link map
218 | *
219 | * @return MapResult
220 | */
221 | public function map(): MapResult
222 | {
223 | $scan = $this->getScanResults();
224 | $result = parent::map();
225 |
226 | // Map XML manifest
227 | if (!empty($this->xmlManifestPath))
228 | {
229 | $result->files = array_merge($result->files, [
230 | $this->xmlManifestPath => $this->siteRoot . '/administrator/manifests/packages/' . $scan->getJoomlaExtensionName() . '.xml',
231 | ]);
232 | }
233 |
234 | // Map script file
235 | if ($scriptPath = $this->getScriptAbsolutePath($scan))
236 | {
237 | $result->files = array_merge($result->files, [
238 | $scriptPath => $this->siteRoot . '/administrator/manifests/packages/' . $scan->getJoomlaExtensionName() . '/' . basename($scriptPath),
239 | ]);
240 | }
241 |
242 | return $result;
243 | }
244 | }
--------------------------------------------------------------------------------
/src/LinkLib/Scanner/Plugin.php:
--------------------------------------------------------------------------------
1 | manifestExtensionType = 'plugin';
32 |
33 | parent::__construct($extensionRoot, $languageRoot);
34 | }
35 |
36 | /**
37 | * Scans the extension for files and folders to link
38 | *
39 | * @return ScanResult
40 | */
41 | public function scan()
42 | {
43 | // Get the XML manifest
44 | $xmlDoc = $this->getXMLManifest();
45 |
46 | if (empty($xmlDoc))
47 | {
48 | throw new RuntimeException("Cannot get XML manifest for plugin in {$this->extensionRoot}");
49 | }
50 |
51 | // Intiialize the result
52 | $result = new ScanResult();
53 | $result->extensionType = 'plugin';
54 |
55 | // Get the extension name
56 | $files = $xmlDoc->getElementsByTagName('files')->item(0)->childNodes;
57 | $plugin = null;
58 |
59 | /** @var \DOMElement $file */
60 | foreach ($files as $file)
61 | {
62 | if ($file->hasAttributes())
63 | {
64 | $plugin = $file->getAttribute('plugin');
65 |
66 | break;
67 | }
68 | }
69 |
70 | /**
71 | * Native Joomla 4 plugins do not have the plugin attribute in a file entry. They have a namespace element under
72 | * the root and the plugin name is the name of the folder.
73 | */
74 | if (is_null($plugin))
75 | {
76 | $hasNamespace = $xmlDoc->getElementsByTagName('namespace')->count();
77 |
78 | if ($hasNamespace)
79 | {
80 | $plugin = basename($this->extensionRoot);
81 | }
82 | }
83 |
84 | if (is_null($plugin))
85 | {
86 | throw new RuntimeException("Cannot find the plugin name in the XML manifest for {$this->extensionRoot}");
87 | }
88 |
89 | $result->extension = $plugin;
90 |
91 | // Is this is a site or administrator module?
92 | $result->pluginFolder = $xmlDoc->documentElement->getAttribute('group');
93 |
94 | // Get the main folder to link
95 | $result->siteFolder = $this->extensionRoot;
96 |
97 | // Get the media folder
98 | $result->mediaFolder = null;
99 | $result->mediaDestination = null;
100 | $allMediaTags = $xmlDoc->getElementsByTagName('media');
101 |
102 | if ($allMediaTags->length >= 1)
103 | {
104 | $result->mediaFolder = $this->extensionRoot . '/' . (string) $allMediaTags->item(0)
105 | ->getAttribute('folder');
106 | $result->mediaDestination = $allMediaTags->item(0)->getAttribute('destination');
107 | }
108 |
109 | // Get the tag
110 | $xpath = new \DOMXPath($xmlDoc);
111 | $languagesNodes = $xpath->query('/extension/languages');
112 |
113 | foreach ($languagesNodes as $node)
114 | {
115 | list($languageRoot, $languageFiles) = $this->scanLanguageNode($node);
116 |
117 | if (empty($languageFiles))
118 | {
119 | continue;
120 | }
121 |
122 | // Plugin language files always go to the backend language folder
123 | $result->adminLangFiles = $languageFiles;
124 | $result->adminLangPath = $languageRoot;
125 | }
126 |
127 | // Scan language files in a separate root, if one is specified
128 | if (!empty($this->languageRoot))
129 | {
130 | $langPath = $this->languageRoot . '/plugins/' . $result->pluginFolder . '/' . $result->extension;
131 | $langFiles = $this->scanLanguageFolder($langPath);
132 |
133 | if (!empty($langFiles))
134 | {
135 | $result->adminLangPath = $langPath;
136 | $result->adminLangFiles = $langFiles;
137 | }
138 | }
139 |
140 |
141 | return $result;
142 | }
143 |
144 | /**
145 | * Parses the last scan and generates a link map
146 | *
147 | * @return MapResult
148 | */
149 | public function map()
150 | {
151 | $scan = $this->getScanResults();
152 | $result = parent::map();
153 |
154 | $basePath = $this->siteRoot . '/plugins/' . $scan->pluginFolder . '/' . $scan->extension;
155 |
156 | // Frontend and backend directories
157 | $dirs = [
158 | $scan->siteFolder => $basePath
159 | ];
160 |
161 | $result->dirs = array_merge($result->dirs, $dirs);
162 |
163 | return $result;
164 | }
165 |
166 | /**
167 | * Detect extensions of type Plugin in the repository and return an array of ScannerInterface objects for them.
168 | *
169 | * @param string $repositoryRoot The repository root to scan
170 | *
171 | * @return ScannerInterface[]
172 | */
173 | public static function detect($repositoryRoot): array
174 | {
175 | $path = $repositoryRoot . '/plugins';
176 | $extensions = [];
177 |
178 | if (!is_dir($path))
179 | {
180 | return $extensions;
181 | }
182 |
183 | // Scan the "plugins" repo folder for the sections (user, system, content, quickicon, somethingCustom, ...)
184 | $outerDi = new \DirectoryIterator($path);
185 |
186 | foreach ($outerDi as $sectionFolder)
187 | {
188 | if ($sectionFolder->isDot() || !$sectionFolder->isDir())
189 | {
190 | continue;
191 | }
192 |
193 | $sectionPath = $sectionFolder->getRealPath();
194 | $section = $sectionFolder->getFilename();
195 |
196 | // Scan all plugin folders inside that section
197 | $allPluginFolders = new \DirectoryIterator($sectionPath);
198 |
199 | foreach ($allPluginFolders as $pluginFolder)
200 | {
201 | if ($pluginFolder->isDot() || !$pluginFolder->isDir())
202 | {
203 | continue;
204 | }
205 |
206 | $extName = $pluginFolder->getFilename();
207 |
208 | // Figure out the language root to use
209 | $languageRoot = null;
210 | $translationsRoot = self::getTranslationsRoot($repositoryRoot);
211 |
212 | if ($translationsRoot)
213 | {
214 | $languageRoot = $translationsRoot . '/plugins/' . $section . '/' . $extName;
215 |
216 | if (!is_dir($languageRoot))
217 | {
218 | $languageRoot = null;
219 | }
220 | }
221 |
222 | // Get the extension ScannerInterface object
223 | $extension = new Plugin($pluginFolder->getRealPath(), $languageRoot);
224 | $extensions[] = $extension;
225 | }
226 | }
227 |
228 | return $extensions;
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/LinkLib/Scanner/Template.php:
--------------------------------------------------------------------------------
1 | manifestExtensionType = 'template';
32 |
33 | parent::__construct($extensionRoot, $languageRoot);
34 | }
35 |
36 | /**
37 | * Scans the extension for files and folders to link
38 | *
39 | * @return ScanResult
40 | */
41 | public function scan()
42 | {
43 | // Get the XML manifest
44 | $xmlDoc = $this->getXMLManifest();
45 |
46 | if (empty($xmlDoc))
47 | {
48 | throw new RuntimeException("Cannot get XML manifest for template in {$this->extensionRoot}");
49 | }
50 |
51 | // Intiialize the result
52 | $result = new ScanResult();
53 | $result->extensionType = 'template';
54 |
55 | // Get the extension name
56 | $template = strtolower($xmlDoc->getElementsByTagName('name')->item(0)->nodeValue);
57 |
58 | if (is_null($template))
59 | {
60 | throw new RuntimeException("Cannot find the template name in the XML manifest for {$this->extensionRoot}");
61 | }
62 |
63 | $result->extension = $template;
64 |
65 | // Is this is a site or administrator template?
66 | $isSite = $xmlDoc->documentElement->getAttribute('client') == 'site';
67 |
68 | // Get the main folder to link
69 | if ($isSite)
70 | {
71 | $result->siteFolder = $this->extensionRoot;
72 | }
73 | else
74 | {
75 | $result->adminFolder = $this->extensionRoot;
76 | }
77 |
78 | // Get the media folder
79 | $result->mediaFolder = null;
80 | $result->mediaDestination = null;
81 | $allMediaTags = $xmlDoc->getElementsByTagName('media');
82 |
83 | if ($allMediaTags->length >= 1)
84 | {
85 | $result->mediaFolder = $this->extensionRoot . '/' . (string) $allMediaTags->item(0)
86 | ->getAttribute('folder');
87 | $result->mediaDestination = $allMediaTags->item(0)->getAttribute('destination');
88 | }
89 |
90 | // Get the tag
91 | $xpath = new \DOMXPath($xmlDoc);
92 | $languagesNodes = $xpath->query('/extension/languages');
93 |
94 | foreach ($languagesNodes as $node)
95 | {
96 | list($languageRoot, $languageFiles) = $this->scanLanguageNode($node);
97 |
98 | if (empty($languageFiles))
99 | {
100 | continue;
101 | }
102 |
103 | if ($isSite)
104 | {
105 | $result->siteLangFiles = $languageFiles;
106 | $result->siteLangPath = $languageRoot;
107 |
108 | continue;
109 | }
110 |
111 | $result->adminLangFiles = $languageFiles;
112 | $result->adminLangPath = $languageRoot;
113 | }
114 |
115 | // Scan language files in a separate root, if one is specified
116 | if (!empty($this->languageRoot))
117 | {
118 | $langPath = $this->languageRoot . '/templates/';
119 | $langPath .= $isSite ? 'site/' : 'admin/';
120 | $langPath .= $template;
121 | $langFiles = $this->scanLanguageFolder($langPath);
122 |
123 | if (!empty($langFiles))
124 | {
125 | if ($isSite)
126 | {
127 | $result->siteLangPath = $langPath;
128 | $result->siteLangFiles = $langFiles;
129 | }
130 | else
131 | {
132 | $result->adminLangPath = $langPath;
133 | $result->adminLangFiles = $langFiles;
134 | }
135 | }
136 | }
137 |
138 | return $result;
139 | }
140 |
141 | /**
142 | * Parses the last scan and generates a link map
143 | *
144 | * @return MapResult
145 | */
146 | public function map()
147 | {
148 | $scan = $this->getScanResults();
149 | $result = parent::map();
150 |
151 | $source = $scan->siteFolder;
152 | $basePath = $this->siteRoot . '/';
153 |
154 | if (!empty($scan->adminFolder))
155 | {
156 | $basePath .= 'administrator/';
157 | $source = $scan->adminFolder;
158 | }
159 |
160 | $basePath .= 'templates/' . $scan->extension;
161 |
162 | // Frontend and backend directories
163 | $dirs = [
164 | $source => $basePath
165 | ];
166 |
167 | $result->dirs = array_merge($result->dirs, $dirs);
168 |
169 | return $result;
170 | }
171 |
172 | /**
173 | * Detect extensions of type Template in the repository and return an array of ScannerInterface objects for them.
174 | *
175 | * @param string $repositoryRoot The repository root to scan
176 | *
177 | * @return ScannerInterface[]
178 | */
179 | public static function detect($repositoryRoot): array
180 | {
181 | $path = $repositoryRoot . '/templates';
182 | $sections = ['site', 'admin'];
183 | $extensions = [];
184 |
185 | if (!is_dir($path))
186 | {
187 | return $extensions;
188 | }
189 |
190 | // Loop both sections (site and admin)
191 | foreach ($sections as $section)
192 | {
193 | $sectionPath = $path . '/' . $section;
194 |
195 | if (!is_dir($sectionPath))
196 | {
197 | continue;
198 | }
199 |
200 | // Loop all templates in the section
201 | $di = new \DirectoryIterator($sectionPath);
202 |
203 | foreach ($di as $folder)
204 | {
205 | if ($folder->isDot() || !$folder->isDir())
206 | {
207 | continue;
208 | }
209 |
210 | $extName = $folder->getFilename();
211 |
212 | // Figure out the language root to use
213 | $languageRoot = null;
214 | $translationsRoot = self::getTranslationsRoot($repositoryRoot);
215 |
216 | if ($translationsRoot)
217 | {
218 | $languageRoot = $translationsRoot . '/templates/' . $section . '/' . $extName;
219 |
220 | if (!is_dir($languageRoot))
221 | {
222 | $languageRoot = null;
223 | }
224 | }
225 |
226 | // Get the extension ScannerInterface object
227 | $extension = new Template($folder->getRealPath(), $languageRoot);
228 | $extensions[] = $extension;
229 | }
230 | }
231 |
232 | return $extensions;
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/LinkLib/ScannerInterface.php:
--------------------------------------------------------------------------------
1 |