258 lines
7.1 KiB
PHP
258 lines
7.1 KiB
PHP
|
<?php
|
||
|
|
||
|
/*
|
||
|
* This file is part of the Fxp Composer Asset Plugin package.
|
||
|
*
|
||
|
* (c) François Pluchino <francois.pluchino@gmail.com>
|
||
|
*
|
||
|
* For the full copyright and license information, please view the LICENSE
|
||
|
* file that was distributed with this source code.
|
||
|
*/
|
||
|
|
||
|
namespace Fxp\Composer\AssetPlugin\Repository\Vcs;
|
||
|
|
||
|
use Composer\Cache;
|
||
|
use Composer\Downloader\TransportException;
|
||
|
use Composer\Json\JsonFile;
|
||
|
use Composer\Repository\Vcs\GitHubDriver as BaseGitHubDriver;
|
||
|
use Fxp\Composer\AssetPlugin\Repository\Util as RepoUtil;
|
||
|
|
||
|
/**
|
||
|
* Abstract class for GitHub vcs driver.
|
||
|
*
|
||
|
* @author François Pluchino <francois.pluchino@gmail.com>
|
||
|
*/
|
||
|
abstract class AbstractGitHubDriver extends BaseGitHubDriver
|
||
|
{
|
||
|
/**
|
||
|
* @var Cache
|
||
|
*/
|
||
|
protected $cache;
|
||
|
|
||
|
/**
|
||
|
* @var string|null|false
|
||
|
*/
|
||
|
protected $redirectApi;
|
||
|
|
||
|
public function initialize()
|
||
|
{
|
||
|
if (!isset($this->repoConfig['no-api'])) {
|
||
|
$this->repoConfig['no-api'] = $this->getNoApiOption();
|
||
|
}
|
||
|
|
||
|
parent::initialize();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the no-api repository option.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected function getNoApiOption()
|
||
|
{
|
||
|
$packageName = $this->repoConfig['package-name'];
|
||
|
$opts = RepoUtil::getArrayValue($this->repoConfig, 'vcs-driver-options', array());
|
||
|
$noApiOpt = RepoUtil::getArrayValue($opts, 'github-no-api', array());
|
||
|
$defaultValue = false;
|
||
|
|
||
|
if (is_bool($noApiOpt)) {
|
||
|
$defaultValue = $noApiOpt;
|
||
|
$noApiOpt = array();
|
||
|
}
|
||
|
|
||
|
$noApiOpt['default'] = (bool) RepoUtil::getArrayValue($noApiOpt, 'default', $defaultValue);
|
||
|
$noApiOpt['packages'] = (array) RepoUtil::getArrayValue($noApiOpt, 'packages', array());
|
||
|
|
||
|
return (bool) RepoUtil::getArrayValue($noApiOpt['packages'], $packageName, $defaultValue);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the remote content.
|
||
|
*
|
||
|
* @param string $url The URL of content
|
||
|
* @param bool $fetchingRepoData Fetching the repo data or not
|
||
|
*
|
||
|
* @return mixed The result
|
||
|
*/
|
||
|
protected function getContents($url, $fetchingRepoData = false)
|
||
|
{
|
||
|
$url = $this->getValidContentUrl($url);
|
||
|
|
||
|
if (null !== $this->redirectApi) {
|
||
|
return parent::getContents($url, $fetchingRepoData);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
$contents = $this->getRemoteContents($url);
|
||
|
$this->redirectApi = false;
|
||
|
|
||
|
return $contents;
|
||
|
} catch (TransportException $e) {
|
||
|
if ($this->hasRedirectUrl($url)) {
|
||
|
$url = $this->getValidContentUrl($url);
|
||
|
}
|
||
|
|
||
|
return parent::getContents($url, $fetchingRepoData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $url The url
|
||
|
*
|
||
|
* @return string The url redirected
|
||
|
*/
|
||
|
protected function getValidContentUrl($url)
|
||
|
{
|
||
|
if (null === $this->redirectApi && false !== $redirectApi = $this->cache->read('redirect-api')) {
|
||
|
$this->redirectApi = $redirectApi;
|
||
|
}
|
||
|
|
||
|
if (is_string($this->redirectApi) && 0 === strpos($url, $this->getRepositoryApiUrl())) {
|
||
|
$url = $this->redirectApi.substr($url, strlen($this->getRepositoryApiUrl()));
|
||
|
}
|
||
|
|
||
|
return $url;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if the driver must find the new url.
|
||
|
*
|
||
|
* @param string $url The url
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected function hasRedirectUrl($url)
|
||
|
{
|
||
|
if (null === $this->redirectApi && 0 === strpos($url, $this->getRepositoryApiUrl())) {
|
||
|
$this->redirectApi = $this->getNewRepositoryUrl();
|
||
|
|
||
|
if (is_string($this->redirectApi)) {
|
||
|
$this->cache->write('redirect-api', $this->redirectApi);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return is_string($this->redirectApi);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the new url of repository.
|
||
|
*
|
||
|
* @return string|false The new url or false if there is not a new url
|
||
|
*/
|
||
|
protected function getNewRepositoryUrl()
|
||
|
{
|
||
|
try {
|
||
|
$this->getRemoteContents($this->getRepositoryUrl());
|
||
|
$headers = $this->remoteFilesystem->getLastHeaders();
|
||
|
|
||
|
if (!empty($headers[0]) && preg_match('{^HTTP/\S+ (30[1278])}i', $headers[0], $match)) {
|
||
|
array_shift($headers);
|
||
|
|
||
|
return $this->findNewLocationInHeader($headers);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
} catch (\Exception $ex) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find the new url api in the header.
|
||
|
*
|
||
|
* @param array $headers The http header
|
||
|
*
|
||
|
* @return string|false
|
||
|
*/
|
||
|
protected function findNewLocationInHeader(array $headers)
|
||
|
{
|
||
|
$url = false;
|
||
|
|
||
|
foreach ($headers as $header) {
|
||
|
if (0 === strpos($header, 'Location:')) {
|
||
|
$newUrl = trim(substr($header, 9));
|
||
|
preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $newUrl, $match);
|
||
|
$owner = $match[3];
|
||
|
$repository = $match[4];
|
||
|
$paramPos = strpos($repository, '?');
|
||
|
$repository = is_int($paramPos) ? substr($match[4], 0, $paramPos) : $repository;
|
||
|
$url = $this->getRepositoryApiUrl($owner, $repository);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $url;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the url API of the repository.
|
||
|
*
|
||
|
* @param string $owner
|
||
|
* @param string $repository
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function getRepositoryApiUrl($owner = null, $repository = null)
|
||
|
{
|
||
|
$owner = null !== $owner ? $owner : $this->owner;
|
||
|
$repository = null !== $repository ? $repository : $this->repository;
|
||
|
|
||
|
return $this->getApiUrl().'/repos/'.$owner.'/'.$repository;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the remote content.
|
||
|
*
|
||
|
* @param string $url
|
||
|
*
|
||
|
* @return bool|string
|
||
|
*/
|
||
|
protected function getRemoteContents($url)
|
||
|
{
|
||
|
return $this->remoteFilesystem->getContents($this->originUrl, $url, false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* {@inheritdoc}
|
||
|
*/
|
||
|
public function getBranches()
|
||
|
{
|
||
|
if ($this->gitDriver) {
|
||
|
return $this->gitDriver->getBranches();
|
||
|
}
|
||
|
|
||
|
if (null === $this->branches) {
|
||
|
$this->branches = array();
|
||
|
$resource = $this->getApiUrl().'/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100';
|
||
|
$branchBlacklist = 'gh-pages' === $this->getRootIdentifier() ? array() : array('gh-pages');
|
||
|
|
||
|
$this->doAddBranches($resource, $branchBlacklist);
|
||
|
}
|
||
|
|
||
|
return $this->branches;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Push the list of all branch.
|
||
|
*
|
||
|
* @param string $resource
|
||
|
* @param array $branchBlacklist
|
||
|
*/
|
||
|
protected function doAddBranches($resource, array $branchBlacklist)
|
||
|
{
|
||
|
do {
|
||
|
$branchData = JsonFile::parseJson((string) $this->getContents($resource), $resource);
|
||
|
|
||
|
foreach ($branchData as $branch) {
|
||
|
$name = substr($branch['ref'], 11);
|
||
|
|
||
|
if (!in_array($name, $branchBlacklist)) {
|
||
|
$this->branches[$name] = $branch['object']['sha'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$resource = $this->getNextPage();
|
||
|
} while ($resource);
|
||
|
}
|
||
|
}
|