1<?php
2
3/*
4 * This file is part of the Fxp Composer Asset Plugin package.
5 *
6 * (c) François Pluchino <francois.pluchino@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Fxp\Composer\AssetPlugin\Converter;
13
14use Composer\Config;
15use Composer\IO\NullIO;
16use Composer\Repository\Vcs\VcsDriverInterface;
17use Fxp\Composer\AssetPlugin\Assets;
18use Fxp\Composer\AssetPlugin\Exception\InvalidArgumentException;
19use Fxp\Composer\AssetPlugin\Type\AssetTypeInterface;
20use Fxp\Composer\AssetPlugin\Util\Validator;
21
22/**
23 * Utils for package converter.
24 *
25 * @author François Pluchino <francois.pluchino@gmail.com>
26 */
27abstract class PackageUtil
28{
29    /**
30     * @var string[]
31     */
32    private static $extensions = array(
33        '.zip',
34        '.tar',
35        '.tar.gz',
36        '.tar.bz2',
37        '.tar.Z',
38        '.tar.xz',
39        '.bz2',
40        '.gz',
41    );
42
43    /**
44     * Checks if the version is a URL version.
45     *
46     * @param AssetTypeInterface $assetType  The asset type
47     * @param string             $dependency The dependency
48     * @param string             $version    The version
49     * @param array              $vcsRepos   The list of new vcs configs
50     * @param array              $composer   The partial composer data
51     *
52     * @return string[] The new dependency and the new version
53     */
54    public static function checkUrlVersion(AssetTypeInterface $assetType, $dependency, $version, array &$vcsRepos, array $composer)
55    {
56        if (preg_match('/(\:\/\/)|\@/', $version)) {
57            list($url, $version) = static::splitUrlVersion($version);
58
59            if (!static::isUrlArchive($url) && static::hasUrlDependencySupported($url)) {
60                $vcsRepos[] = array(
61                    'type' => sprintf('%s-vcs', $assetType->getName()),
62                    'url' => $url,
63                    'name' => $assetType->formatComposerName($dependency),
64                );
65            } else {
66                $dependency = static::getUrlFileDependencyName($assetType, $composer, $dependency);
67                $vcsRepos[] = array(
68                    'type' => 'package',
69                    'package' => array(
70                        'name' => $assetType->formatComposerName($dependency),
71                        'type' => $assetType->getComposerType(),
72                        'version' => static::getUrlFileDependencyVersion($assetType, $url, $version),
73                        'dist' => array(
74                            'url' => $url,
75                            'type' => 'file',
76                        ),
77                    ),
78                );
79            }
80        }
81
82        return array($dependency, $version);
83    }
84
85    /**
86     * Check if the url is a url of a archive file.
87     *
88     * @param string $url The url
89     *
90     * @return bool
91     */
92    public static function isUrlArchive($url)
93    {
94        if (0 === strpos($url, 'http')) {
95            foreach (self::$extensions as $extension) {
96                if (substr($url, -\strlen($extension)) === $extension) {
97                    return true;
98                }
99            }
100        }
101
102        return false;
103    }
104
105    /**
106     * Checks if the version is a alias version.
107     *
108     * @param AssetTypeInterface $assetType  The asset type
109     * @param string             $dependency The dependency
110     * @param string             $version    The version
111     *
112     * @return string[] The new dependency and the new version
113     */
114    public static function checkAliasVersion(AssetTypeInterface $assetType, $dependency, $version)
115    {
116        $pos = strpos($version, '#');
117
118        if ($pos > 0 && !preg_match('{[0-9a-f]{40}$}', $version)) {
119            $dependency = substr($version, 0, $pos);
120            $version = substr($version, $pos);
121            $searchVerion = substr($version, 1);
122
123            if (false === strpos($version, '*') && Validator::validateTag($searchVerion, $assetType)) {
124                $dependency .= '-'.str_replace('#', '', $version);
125            }
126        }
127
128        return array($dependency, $version);
129    }
130
131    /**
132     * Convert the dependency version.
133     *
134     * @param AssetTypeInterface $assetType  The asset type
135     * @param string             $dependency The dependency
136     * @param string             $version    The version
137     *
138     * @return string[] The new dependency and the new version
139     */
140    public static function convertDependencyVersion(AssetTypeInterface $assetType, $dependency, $version)
141    {
142        $containsHash = false !== strpos($version, '#');
143        $version = str_replace('#', '', $version);
144        $version = empty($version) ? '*' : trim($version);
145        $searchVersion = str_replace(array(' ', '<', '>', '=', '^', '~'), '', $version);
146
147        // sha version or branch version
148        // sha size: 4-40. See https://git-scm.com/book/tr/v2/Git-Tools-Revision-Selection#_short_sha_1
149        if ($containsHash && preg_match('{^[0-9a-f]{4,40}$}', $version)) {
150            $version = 'dev-default#'.$version;
151        } elseif ('*' !== $version && !Validator::validateTag($searchVersion, $assetType) && !static::depIsRange($version)) {
152            $version = static::convertBrachVersion($assetType, $version);
153        }
154
155        return array($dependency, $version);
156    }
157
158    /**
159     * Converts the simple key of package.
160     *
161     * @param array  $asset       The asset data
162     * @param string $assetKey    The asset key
163     * @param array  $composer    The composer data
164     * @param string $composerKey The composer key
165     */
166    public static function convertStringKey(array $asset, $assetKey, array &$composer, $composerKey)
167    {
168        if (isset($asset[$assetKey])) {
169            $composer[$composerKey] = $asset[$assetKey];
170        }
171    }
172
173    /**
174     * Converts the simple key of package.
175     *
176     * @param array  $asset       The asset data
177     * @param string $assetKey    The asset key
178     * @param array  $composer    The composer data
179     * @param array  $composerKey The array with composer key name and closure
180     *
181     * @throws InvalidArgumentException When the 'composerKey' argument of asset packager converter is not an string or an array with the composer key and closure
182     */
183    public static function convertArrayKey(array $asset, $assetKey, array &$composer, $composerKey)
184    {
185        if (2 !== \count($composerKey)
186            || !\is_string($composerKey[0]) || !$composerKey[1] instanceof \Closure) {
187            throw new InvalidArgumentException('The "composerKey" argument of asset packager converter must be an string or an array with the composer key and closure');
188        }
189
190        $closure = $composerKey[1];
191        $composerKey = $composerKey[0];
192        $data = isset($asset[$assetKey]) ? $asset[$assetKey] : null;
193        $previousData = isset($composer[$composerKey]) ? $composer[$composerKey] : null;
194        $data = $closure($data, $previousData);
195
196        if (null !== $data) {
197            $composer[$composerKey] = $data;
198        }
199    }
200
201    /**
202     * Split the URL and version.
203     *
204     * @param string $version The url and version (in the same string)
205     *
206     * @return string[] The url and version
207     */
208    protected static function splitUrlVersion($version)
209    {
210        $pos = strpos($version, '#');
211
212        // number version or empty version
213        if (false !== $pos) {
214            $url = substr($version, 0, $pos);
215            $version = substr($version, $pos);
216        } else {
217            $url = $version;
218            $version = '#';
219        }
220
221        return array($url, $version);
222    }
223
224    /**
225     * Get the name of url file dependency.
226     *
227     * @param AssetTypeInterface $assetType  The asset type
228     * @param array              $composer   The partial composer
229     * @param string             $dependency The dependency name
230     *
231     * @return string The dependency name
232     */
233    protected static function getUrlFileDependencyName(AssetTypeInterface $assetType, array $composer, $dependency)
234    {
235        $prefix = isset($composer['name'])
236            ? substr($composer['name'], \strlen($assetType->getComposerVendorName()) + 1).'-'
237            : '';
238
239        return $prefix.$dependency.'-file';
240    }
241
242    /**
243     * Get the version of url file dependency.
244     *
245     * @param AssetTypeInterface $assetType The asset type
246     * @param string             $url       The url
247     * @param string             $version   The version
248     *
249     * @return string The version
250     */
251    protected static function getUrlFileDependencyVersion(AssetTypeInterface $assetType, $url, $version)
252    {
253        if ('#' !== $version) {
254            return substr($version, 1);
255        }
256
257        if (preg_match('/(\d+)(\.\d+)(\.\d+)?(\.\d+)?/', $url, $match)) {
258            return $assetType->getVersionConverter()->convertVersion($match[0]);
259        }
260
261        return '0.0.0.0';
262    }
263
264    /**
265     * Check if url is supported by vcs drivers.
266     *
267     * @param string $url The url
268     *
269     * @return bool
270     */
271    protected static function hasUrlDependencySupported($url)
272    {
273        $io = new NullIO();
274        $config = new Config();
275
276        /** @var VcsDriverInterface $driver */
277        foreach (Assets::getVcsDrivers() as $driver) {
278            $supported = $driver::supports($io, $config, $url);
279
280            if ($supported) {
281                return true;
282            }
283        }
284
285        return false;
286    }
287
288    /**
289     * Check if the version of dependency is a range version.
290     *
291     * @param string $version
292     *
293     * @return bool
294     */
295    protected static function depIsRange($version)
296    {
297        $version = trim($version);
298
299        return (bool) preg_match('/[\<\>\=\^\~\ ]/', $version);
300    }
301
302    /**
303     * Convert the dependency branch version.
304     *
305     * @param AssetTypeInterface $assetType The asset type
306     * @param string             $version   The version
307     *
308     * @return string
309     */
310    protected static function convertBrachVersion(AssetTypeInterface $assetType, $version)
311    {
312        $oldVersion = $version;
313        $version = 'dev-'.$assetType->getVersionConverter()->convertVersion($version);
314
315        if (!Validator::validateBranch($oldVersion)) {
316            $version .= ' || '.$oldVersion;
317        }
318
319        return $version;
320    }
321}
322