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\Repository\Vcs; 13 14use Composer\Cache; 15use Composer\Downloader\TransportException; 16use Composer\Json\JsonFile; 17use Composer\Repository\Vcs\GitHubDriver as BaseGitHubDriver; 18use Fxp\Composer\AssetPlugin\Repository\Util as RepoUtil; 19 20/** 21 * Abstract class for GitHub vcs driver. 22 * 23 * @author François Pluchino <francois.pluchino@gmail.com> 24 */ 25abstract class AbstractGitHubDriver extends BaseGitHubDriver 26{ 27 /** 28 * @var Cache 29 */ 30 protected $cache; 31 32 /** 33 * @var null|false|string 34 */ 35 protected $redirectApi; 36 37 public function initialize() 38 { 39 if (!isset($this->repoConfig['no-api'])) { 40 $this->repoConfig['no-api'] = $this->getNoApiOption(); 41 } 42 43 parent::initialize(); 44 } 45 46 /** 47 * {@inheritdoc} 48 */ 49 public function getBranches() 50 { 51 if ($this->gitDriver) { 52 return $this->gitDriver->getBranches(); 53 } 54 55 if (null === $this->branches) { 56 $this->branches = array(); 57 $resource = $this->getApiUrl().'/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100'; 58 $branchBlacklist = 'gh-pages' === $this->getRootIdentifier() ? array() : array('gh-pages'); 59 60 $this->doAddBranches($resource, $branchBlacklist); 61 } 62 63 return $this->branches; 64 } 65 66 /** 67 * Get the no-api repository option. 68 * 69 * @return bool 70 */ 71 protected function getNoApiOption() 72 { 73 $packageName = $this->repoConfig['package-name']; 74 $opts = RepoUtil::getArrayValue($this->repoConfig, 'vcs-driver-options', array()); 75 $noApiOpt = RepoUtil::getArrayValue($opts, 'github-no-api', array()); 76 $defaultValue = false; 77 78 if (\is_bool($noApiOpt)) { 79 $defaultValue = $noApiOpt; 80 $noApiOpt = array(); 81 } 82 83 $noApiOpt['default'] = (bool) RepoUtil::getArrayValue($noApiOpt, 'default', $defaultValue); 84 $noApiOpt['packages'] = (array) RepoUtil::getArrayValue($noApiOpt, 'packages', array()); 85 86 return (bool) RepoUtil::getArrayValue($noApiOpt['packages'], $packageName, $defaultValue); 87 } 88 89 /** 90 * Get the remote content. 91 * 92 * @param string $url The URL of content 93 * @param bool $fetchingRepoData Fetching the repo data or not 94 * 95 * @return mixed The result 96 */ 97 protected function getContents($url, $fetchingRepoData = false) 98 { 99 $url = $this->getValidContentUrl($url); 100 101 if (null !== $this->redirectApi) { 102 return parent::getContents($url, $fetchingRepoData); 103 } 104 105 try { 106 $contents = $this->getRemoteContents($url); 107 $this->redirectApi = false; 108 109 return $contents; 110 } catch (TransportException $e) { 111 if ($this->hasRedirectUrl($url)) { 112 $url = $this->getValidContentUrl($url); 113 } 114 115 return parent::getContents($url, $fetchingRepoData); 116 } 117 } 118 119 /** 120 * @param string $url The url 121 * 122 * @return string The url redirected 123 */ 124 protected function getValidContentUrl($url) 125 { 126 if (null === $this->redirectApi && false !== $redirectApi = $this->cache->read('redirect-api')) { 127 $this->redirectApi = $redirectApi; 128 } 129 130 if (\is_string($this->redirectApi) && 0 === strpos($url, $this->getRepositoryApiUrl())) { 131 $url = $this->redirectApi.substr($url, \strlen($this->getRepositoryApiUrl())); 132 } 133 134 return $url; 135 } 136 137 /** 138 * Check if the driver must find the new url. 139 * 140 * @param string $url The url 141 * 142 * @return bool 143 */ 144 protected function hasRedirectUrl($url) 145 { 146 if (null === $this->redirectApi && 0 === strpos($url, $this->getRepositoryApiUrl())) { 147 $this->redirectApi = $this->getNewRepositoryUrl(); 148 149 if (\is_string($this->redirectApi)) { 150 $this->cache->write('redirect-api', $this->redirectApi); 151 } 152 } 153 154 return \is_string($this->redirectApi); 155 } 156 157 /** 158 * Get the new url of repository. 159 * 160 * @return false|string The new url or false if there is not a new url 161 */ 162 protected function getNewRepositoryUrl() 163 { 164 try { 165 $this->getRemoteContents($this->getRepositoryUrl()); 166 $headers = $this->remoteFilesystem->getLastHeaders(); 167 168 if (!empty($headers[0]) && preg_match('{^HTTP/\S+ (30[1278])}i', $headers[0], $match)) { 169 array_shift($headers); 170 171 return $this->findNewLocationInHeader($headers); 172 } 173 174 return false; 175 } catch (\Exception $ex) { 176 return false; 177 } 178 } 179 180 /** 181 * Find the new url api in the header. 182 * 183 * @param array $headers The http header 184 * 185 * @return false|string 186 */ 187 protected function findNewLocationInHeader(array $headers) 188 { 189 $url = false; 190 191 foreach ($headers as $header) { 192 if (0 === strpos($header, 'Location:')) { 193 $newUrl = trim(substr($header, 9)); 194 preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $newUrl, $match); 195 $owner = $match[3]; 196 $repository = $match[4]; 197 $paramPos = strpos($repository, '?'); 198 $repository = \is_int($paramPos) ? substr($match[4], 0, $paramPos) : $repository; 199 $url = $this->getRepositoryApiUrl($owner, $repository); 200 201 break; 202 } 203 } 204 205 return $url; 206 } 207 208 /** 209 * Get the url API of the repository. 210 * 211 * @param string $owner 212 * @param string $repository 213 * 214 * @return string 215 */ 216 protected function getRepositoryApiUrl($owner = null, $repository = null) 217 { 218 $owner = null !== $owner ? $owner : $this->owner; 219 $repository = null !== $repository ? $repository : $this->repository; 220 221 return $this->getApiUrl().'/repos/'.$owner.'/'.$repository; 222 } 223 224 /** 225 * Get the remote content. 226 * 227 * @param string $url 228 * 229 * @return bool|string 230 */ 231 protected function getRemoteContents($url) 232 { 233 return $this->remoteFilesystem->getContents($this->originUrl, $url, false); 234 } 235 236 /** 237 * Push the list of all branch. 238 * 239 * @param string $resource 240 * @param array $branchBlacklist 241 */ 242 protected function doAddBranches($resource, array $branchBlacklist) 243 { 244 do { 245 $branchData = JsonFile::parseJson((string) $this->getContents($resource), $resource); 246 247 foreach ($branchData as $branch) { 248 $name = substr($branch['ref'], 11); 249 250 if (!\in_array($name, $branchBlacklist, true)) { 251 $this->branches[$name] = $branch['object']['sha']; 252 } 253 } 254 255 $resource = $this->getNextPage(); 256 } while ($resource); 257 } 258} 259