├── .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 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 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 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 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 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 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 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 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 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/17.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/18.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/19.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 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 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/21.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/22.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/23.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/25.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/26.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/27.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/28.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/29.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 15 | 20 | 21 | 22 | 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 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /phing/bin/images/callouts/30.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 17 | 20 | 21 | 22 | 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 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 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 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 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 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 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 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 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 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 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 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 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, '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 |