* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Fxp\Composer\AssetPlugin\Repository; use Composer\Config; use Composer\DependencyResolver\Pool; use Composer\Downloader\TransportException; use Composer\EventDispatcher\EventDispatcher; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Repository\ComposerRepository; use Composer\Repository\RepositoryManager; use Fxp\Composer\AssetPlugin\Assets; use Fxp\Composer\AssetPlugin\Type\AssetTypeInterface; /** * Abstract assets repository. * * @author François Pluchino */ abstract class AbstractAssetsRepository extends ComposerRepository { /** * @var AssetTypeInterface */ protected $assetType; /** * @var AssetVcsRepository[] */ protected $repos; /** * @var bool */ protected $searchable; /** * @var bool */ protected $fallbackProviders; /** * @var RepositoryManager */ protected $repositoryManager; /** * @var AssetRepositoryManager */ protected $assetRepositoryManager; /** * @var VcsPackageFilter */ protected $packageFilter; /** * Constructor. * * @param array $repoConfig * @param IOInterface $io * @param Config $config * @param EventDispatcher $eventDispatcher */ public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null) { $repoConfig = array_merge($repoConfig, array( 'url' => $this->getUrl(), )); $this->assetRepositoryManager = $repoConfig['asset-repository-manager']; $this->repositoryManager = $this->assetRepositoryManager->getRepositoryManager(); parent::__construct($repoConfig, $io, $config, $eventDispatcher); $this->assetType = Assets::createType($this->getType()); $this->lazyProvidersUrl = $this->getPackageUrl(); $this->providersUrl = $this->lazyProvidersUrl; $this->searchUrl = $this->getSearchUrl(); $this->hasProviders = true; $this->packageFilter = isset($repoConfig['vcs-package-filter']) ? $repoConfig['vcs-package-filter'] : null; $this->repos = array(); $this->searchable = (bool) $this->getOption($repoConfig['asset-options'], 'searchable', true); $this->fallbackProviders = false; } /** * {@inheritdoc} */ public function search($query, $mode = 0, $type = null) { if (!$this->searchable) { return array(); } $url = str_replace('%query%', urlencode(Util::cleanPackageName($query)), $this->searchUrl); $hostname = (string) parse_url($url, PHP_URL_HOST) ?: $url; $json = (string) $this->rfs->getContents($hostname, $url, false); $data = JsonFile::parseJson($json, $url); $results = array(); /* @var array $item */ foreach ($data as $item) { $results[] = $this->createSearchItem($item); } return $results; } /** * {@inheritdoc} */ public function whatProvides(Pool $pool, $name, $bypassFilters = false) { if (null !== $provides = $this->findWhatProvides($name)) { return $provides; } try { $repoName = Util::convertAliasName($name); $packageName = Util::cleanPackageName($repoName); $packageUrl = $this->buildPackageUrl($packageName); $cacheName = $packageName.'-'.sha1($packageName).'-package.json'; $data = $this->fetchFile($packageUrl, $cacheName); $repo = $this->createVcsRepositoryConfig($data, Util::cleanPackageName($name)); $repo['asset-repository-manager'] = $this->assetRepositoryManager; $repo['vcs-package-filter'] = $this->packageFilter; $repo['vcs-driver-options'] = Util::getArrayValue($this->repoConfig, 'vcs-driver-options', array()); Util::addRepository($this->io, $this->repositoryManager, $this->repos, $name, $repo, $pool); $this->providers[$name] = array(); } catch (\Exception $ex) { $this->whatProvidesManageException($pool, $name, $ex); } return $this->providers[$name]; } /** * {@inheritdoc} */ public function getMinimalPackages() { return array(); } /** * Build the package url. * * @param string $packageName The package name * * @return string */ protected function buildPackageUrl($packageName) { return str_replace('%package%', $packageName, $this->lazyProvidersUrl); } /** * Finds what provides in cache or return empty array if the * name is not a asset package. * * @param string $name * * @return array|null */ protected function findWhatProvides($name) { $assetPrefix = $this->assetType->getComposerVendorName().'/'; if (false === strpos($name, $assetPrefix)) { return array(); } if (isset($this->providers[$name])) { return $this->providers[$name]; } $data = null; if ($this->hasVcsRepository($name)) { $this->providers[$name] = array(); $data = $this->providers[$name]; } return $data; } /** * Checks if the package vcs repository is already include in repository manager. * * @param string $name The package name of the vcs repository * * @return bool */ protected function hasVcsRepository($name) { foreach ($this->repositoryManager->getRepositories() as $mRepo) { if ($mRepo instanceof AssetVcsRepository && $name === $mRepo->getComposerPackageName()) { return true; } } return false; } /** * {@inheritdoc} */ protected function loadRootServerFile() { return array( 'providers' => array(), ); } /** * Gets the option. * * @param array $options The options * @param string $key The key * @param mixed $default The default value * * @return mixed The option value or default value if key is not found */ protected function getOption(array $options, $key, $default = null) { if (array_key_exists($key, $options)) { return $options[$key]; } return $default; } /** * Creates the search result item. * * @param array $item The item * * @return array An array('name' => '...', 'description' => '...') */ protected function createSearchItem(array $item) { return array( 'name' => $this->assetType->getComposerVendorName().'/'.$item['name'], 'description' => null, ); } /** * Manage exception for "whatProvides" method. * * @param Pool $pool * @param string $name * @param \Exception $exception * * @throws \Exception When exception is not a TransportException instance */ protected function whatProvidesManageException(Pool $pool, $name, \Exception $exception) { if ($exception instanceof TransportException) { $this->fallbackWathProvides($pool, $name, $exception); return; } throw $exception; } /** * Searchs if the registry has a package with the same name exists with a * different camelcase. * * @param Pool $pool * @param string $name * @param TransportException $ex */ protected function fallbackWathProvides(Pool $pool, $name, TransportException $ex) { $providers = array(); if (404 === $ex->getCode() && !$this->fallbackProviders) { $this->fallbackProviders = true; $repoName = Util::convertAliasName($name); $results = $this->search($repoName); foreach ($results as $item) { if ($name === strtolower($item['name'])) { $providers = $this->whatProvides($pool, $item['name']); break; } } } $this->fallbackProviders = false; $this->providers[$name] = $providers; } /** * Gets the asset type name. * * @return string */ abstract protected function getType(); /** * Gets the URL of repository. * * @return string */ abstract protected function getUrl(); /** * Gets the URL for get the package information. * * @return string */ abstract protected function getPackageUrl(); /** * Gets the URL for get the search result. * * @return string */ abstract protected function getSearchUrl(); /** * Creates a config of vcs repository. * * @param array $data The repository config * @param string $registryName The package name in asset registry * * @return array An array('type' => '...', 'url' => '...') */ abstract protected function createVcsRepositoryConfig(array $data, $registryName = null); }