You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

335 lines
8.8KB

  1. <?php
  2. /*
  3. * This file is part of the Fxp Composer Asset Plugin package.
  4. *
  5. * (c) François Pluchino <francois.pluchino@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Fxp\Composer\AssetPlugin\Repository;
  11. use Composer\Config;
  12. use Composer\DependencyResolver\Pool;
  13. use Composer\Downloader\TransportException;
  14. use Composer\EventDispatcher\EventDispatcher;
  15. use Composer\IO\IOInterface;
  16. use Composer\Json\JsonFile;
  17. use Composer\Repository\ComposerRepository;
  18. use Composer\Repository\RepositoryManager;
  19. use Fxp\Composer\AssetPlugin\Assets;
  20. use Fxp\Composer\AssetPlugin\Type\AssetTypeInterface;
  21. /**
  22. * Abstract assets repository.
  23. *
  24. * @author François Pluchino <francois.pluchino@gmail.com>
  25. */
  26. abstract class AbstractAssetsRepository extends ComposerRepository
  27. {
  28. /**
  29. * @var AssetTypeInterface
  30. */
  31. protected $assetType;
  32. /**
  33. * @var RepositoryManager
  34. */
  35. protected $rm;
  36. /**
  37. * @var AssetVcsRepository[]
  38. */
  39. protected $repos;
  40. /**
  41. * @var bool
  42. */
  43. protected $searchable;
  44. /**
  45. * @var bool
  46. */
  47. protected $fallbackProviders;
  48. /**
  49. * @var RepositoryManager
  50. */
  51. protected $repositoryManager;
  52. /**
  53. * @var VcsPackageFilter
  54. */
  55. protected $packageFilter;
  56. /**
  57. * Constructor.
  58. *
  59. * @param array $repoConfig
  60. * @param IOInterface $io
  61. * @param Config $config
  62. * @param EventDispatcher $eventDispatcher
  63. */
  64. public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
  65. {
  66. $repoConfig = array_merge($repoConfig, array(
  67. 'url' => $this->getUrl(),
  68. ));
  69. $this->repositoryManager = $repoConfig['repository-manager'];
  70. parent::__construct($repoConfig, $io, $config, $eventDispatcher);
  71. $this->assetType = Assets::createType($this->getType());
  72. $this->lazyProvidersUrl = $this->getPackageUrl();
  73. $this->providersUrl = $this->lazyProvidersUrl;
  74. $this->searchUrl = $this->getSearchUrl();
  75. $this->hasProviders = true;
  76. $this->rm = $repoConfig['repository-manager'];
  77. $this->packageFilter = isset($repoConfig['vcs-package-filter'])
  78. ? $repoConfig['vcs-package-filter']
  79. : null;
  80. $this->repos = array();
  81. $this->searchable = (bool) $this->getOption($repoConfig['asset-options'], 'searchable', true);
  82. $this->fallbackProviders = false;
  83. }
  84. /**
  85. * {@inheritdoc}
  86. */
  87. public function search($query, $mode = 0)
  88. {
  89. if (!$this->searchable) {
  90. return array();
  91. }
  92. $url = str_replace('%query%', urlencode(Util::cleanPackageName($query)), $this->searchUrl);
  93. $hostname = (string) parse_url($url, PHP_URL_HOST) ?: $url;
  94. $json = (string) $this->rfs->getContents($hostname, $url, false);
  95. $data = JsonFile::parseJson($json, $url);
  96. $results = array();
  97. /* @var array $item */
  98. foreach ($data as $item) {
  99. $results[] = $this->createSearchItem($item);
  100. }
  101. return $results;
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public function whatProvides(Pool $pool, $name)
  107. {
  108. if (null !== $provides = $this->findWhatProvides($name)) {
  109. return $provides;
  110. }
  111. try {
  112. $repoName = Util::convertAliasName($name);
  113. $packageName = Util::cleanPackageName($repoName);
  114. $packageUrl = str_replace('%package%', $packageName, $this->lazyProvidersUrl);
  115. $cacheName = $packageName.'-'.sha1($packageName).'-package.json';
  116. $data = $this->fetchFile($packageUrl, $cacheName);
  117. $repo = $this->createVcsRepositoryConfig($data, Util::cleanPackageName($name));
  118. $repo['vcs-package-filter'] = $this->packageFilter;
  119. Util::addRepository($this->io, $this->rm, $this->repos, $name, $repo, $pool);
  120. $this->providers[$name] = array();
  121. } catch (\Exception $ex) {
  122. $this->whatProvidesManageException($pool, $name, $ex);
  123. }
  124. return $this->providers[$name];
  125. }
  126. /**
  127. * {@inheritdoc}
  128. */
  129. public function getMinimalPackages()
  130. {
  131. return array();
  132. }
  133. /**
  134. * Finds what provides in cache or return empty array if the
  135. * name is not a asset package.
  136. *
  137. * @param string $name
  138. *
  139. * @return array|null
  140. */
  141. protected function findWhatProvides($name)
  142. {
  143. $assetPrefix = $this->assetType->getComposerVendorName().'/';
  144. if (false === strpos($name, $assetPrefix)) {
  145. return array();
  146. }
  147. if (isset($this->providers[$name])) {
  148. return $this->providers[$name];
  149. }
  150. $data = null;
  151. if ($this->hasVcsRepository($name)) {
  152. $this->providers[$name] = array();
  153. $data = $this->providers[$name];
  154. }
  155. return $data;
  156. }
  157. /**
  158. * Checks if the package vcs repository is already include in repository manager.
  159. *
  160. * @param string $name The package name of the vcs repository
  161. *
  162. * @return bool
  163. */
  164. protected function hasVcsRepository($name)
  165. {
  166. foreach ($this->repositoryManager->getRepositories() as $mRepo) {
  167. if ($mRepo instanceof AssetVcsRepository
  168. && $name === $mRepo->getComposerPackageName()) {
  169. return true;
  170. }
  171. }
  172. return false;
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. protected function loadRootServerFile()
  178. {
  179. return array(
  180. 'providers' => array(),
  181. );
  182. }
  183. /**
  184. * Gets the option.
  185. *
  186. * @param array $options The options
  187. * @param string $key The key
  188. * @param mixed $default The default value
  189. *
  190. * @return mixed The option value or default value if key is not found
  191. */
  192. protected function getOption(array $options, $key, $default = null)
  193. {
  194. if (array_key_exists($key, $options)) {
  195. return $options[$key];
  196. }
  197. return $default;
  198. }
  199. /**
  200. * Creates the search result item.
  201. *
  202. * @param array $item The item
  203. *
  204. * @return array An array('name' => '...', 'description' => '...')
  205. */
  206. protected function createSearchItem(array $item)
  207. {
  208. return array(
  209. 'name' => $this->assetType->getComposerVendorName().'/'.$item['name'],
  210. 'description' => null,
  211. );
  212. }
  213. /**
  214. * Manage exception for "whatProvides" method.
  215. *
  216. * @param Pool $pool
  217. * @param string $name
  218. * @param \Exception $exception
  219. *
  220. * @throws \Exception When exception is not a TransportException instance
  221. */
  222. protected function whatProvidesManageException(Pool $pool, $name, \Exception $exception)
  223. {
  224. if ($exception instanceof TransportException) {
  225. $this->fallbackWathProvides($pool, $name, $exception);
  226. return;
  227. }
  228. throw $exception;
  229. }
  230. /**
  231. * Searchs if the registry has a package with the same name exists with a
  232. * different camelcase.
  233. *
  234. * @param Pool $pool
  235. * @param string $name
  236. * @param TransportException $ex
  237. */
  238. protected function fallbackWathProvides(Pool $pool, $name, TransportException $ex)
  239. {
  240. $providers = array();
  241. if (404 === $ex->getCode() && !$this->fallbackProviders) {
  242. $this->fallbackProviders = true;
  243. $repoName = Util::convertAliasName($name);
  244. $results = $this->search($repoName);
  245. foreach ($results as $item) {
  246. if ($name === strtolower($item['name'])) {
  247. $providers = $this->whatProvides($pool, $item['name']);
  248. break;
  249. }
  250. }
  251. }
  252. $this->fallbackProviders = false;
  253. $this->providers[$name] = $providers;
  254. }
  255. /**
  256. * Gets the asset type name.
  257. *
  258. * @return string
  259. */
  260. abstract protected function getType();
  261. /**
  262. * Gets the URL of repository.
  263. *
  264. * @return string
  265. */
  266. abstract protected function getUrl();
  267. /**
  268. * Gets the URL for get the package information.
  269. *
  270. * @return string
  271. */
  272. abstract protected function getPackageUrl();
  273. /**
  274. * Gets the URL for get the search result.
  275. *
  276. * @return string
  277. */
  278. abstract protected function getSearchUrl();
  279. /**
  280. * Creates a config of vcs repository.
  281. *
  282. * @param array $data The repository config
  283. * @param string $registryName The package name in asset registry
  284. *
  285. * @return array An array('type' => '...', 'url' => '...')
  286. */
  287. abstract protected function createVcsRepositoryConfig(array $data, $registryName = null);
  288. }