2011-04-17 22:14:44 +00:00
< ? php
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Composer\Repository ;
2011-09-20 21:34:06 +00:00
use Composer\Package\Loader\ArrayLoader ;
2012-04-13 00:58:40 +00:00
use Composer\Package\PackageInterface ;
2012-10-13 16:54:48 +00:00
use Composer\Package\AliasPackage ;
2016-02-24 17:27:42 +00:00
use Composer\Package\Version\VersionParser ;
2020-01-16 06:58:50 +00:00
use Composer\Package\Version\StabilityFilter ;
2011-10-01 13:01:33 +00:00
use Composer\Json\JsonFile ;
2012-04-06 20:39:43 +00:00
use Composer\Cache ;
2012-04-09 14:36:06 +00:00
use Composer\Config ;
2019-02-21 13:05:56 +00:00
use Composer\Composer ;
2014-03-01 17:01:44 +00:00
use Composer\Factory ;
2012-03-18 20:04:15 +00:00
use Composer\IO\IOInterface ;
2018-09-12 16:58:54 +00:00
use Composer\Util\HttpDownloader ;
2019-01-17 16:12:33 +00:00
use Composer\Util\Loop ;
2013-11-19 16:45:28 +00:00
use Composer\Plugin\PluginEvents ;
use Composer\Plugin\PreFileDownloadEvent ;
use Composer\EventDispatcher\EventDispatcher ;
2016-01-18 12:25:37 +00:00
use Composer\Downloader\TransportException ;
2015-09-24 14:32:36 +00:00
use Composer\Semver\Constraint\ConstraintInterface ;
use Composer\Semver\Constraint\Constraint ;
2018-11-12 16:21:23 +00:00
use Composer\Semver\Constraint\EmptyConstraint ;
2019-11-14 08:57:59 +00:00
use Composer\Util\Http\Response ;
2020-01-15 11:58:30 +00:00
use Composer\Util\MetadataMinifier ;
2020-01-30 14:50:46 +00:00
use Composer\Util\Url ;
2020-01-30 16:30:38 +00:00
use React\Promise\Promise ;
2011-04-17 22:14:44 +00:00
/**
* @ author Jordi Boggiano < j . boggiano @ seld . be >
*/
2015-11-23 22:18:24 +00:00
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface
2011-04-17 22:14:44 +00:00
{
2018-12-04 16:03:56 +00:00
private $config ;
private $repoConfig ;
private $options ;
private $url ;
private $baseUrl ;
private $io ;
private $httpDownloader ;
2019-01-17 16:12:33 +00:00
private $loop ;
2012-04-06 20:39:43 +00:00
protected $cache ;
2012-04-13 00:58:40 +00:00
protected $notifyUrl ;
2013-03-10 12:27:49 +00:00
protected $searchUrl ;
2020-01-30 08:23:20 +00:00
/** @var string|null a URL containing %package% which can be queried to get providers of a given name */
protected $providersApiUrl ;
2012-10-14 14:33:53 +00:00
protected $hasProviders = false ;
2013-02-21 15:50:04 +00:00
protected $providersUrl ;
2019-01-15 10:40:49 +00:00
protected $availablePackages ;
2013-07-01 23:46:20 +00:00
protected $lazyProvidersUrl ;
2012-10-14 14:33:53 +00:00
protected $providerListing ;
2012-08-20 13:44:45 +00:00
protected $loader ;
2018-12-04 16:03:56 +00:00
private $allowSslDowngrade = false ;
private $eventDispatcher ;
private $sourceMirrors ;
private $distMirrors ;
2012-10-11 19:26:11 +00:00
private $degradedMode = false ;
2012-10-01 11:58:01 +00:00
private $rootData ;
2016-11-30 21:34:59 +00:00
private $hasPartialPackages ;
private $partialPackagesByName ;
2020-01-30 07:40:35 +00:00
2020-05-04 19:58:33 +00:00
/**
* TODO v3 should make this private once we can drop PHP 5.3 support
* @ private
* @ var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times
* useful for v2 metadata repositories with lazy providers
*/
public $freshMetadataUrls = array ();
2020-01-30 07:40:35 +00:00
/**
2020-01-30 13:17:49 +00:00
* TODO v3 should make this private once we can drop PHP 5.3 support
* @ private
2020-01-30 07:40:35 +00:00
* @ var array list of package names which returned a 404 and should not be re - fetched in case loadPackage is called several times
* useful for v2 metadata repositories with lazy providers
*/
2020-01-30 13:17:49 +00:00
public $packagesNotFoundCache = array ();
2018-12-04 10:20:35 +00:00
/**
* TODO v3 should make this private once we can drop PHP 5.3 support
* @ private
*/
public $versionParser ;
2011-04-17 22:14:44 +00:00
2018-11-12 14:34:54 +00:00
public function __construct ( array $repoConfig , IOInterface $io , Config $config , HttpDownloader $httpDownloader , EventDispatcher $eventDispatcher = null )
2011-04-17 22:14:44 +00:00
{
2016-02-27 21:39:03 +00:00
parent :: __construct ();
2012-10-11 18:54:59 +00:00
if ( ! preg_match ( '{^[\w.]+\??://}' , $repoConfig [ 'url' ])) {
2011-12-12 09:57:57 +00:00
// assume http as the default protocol
2012-04-09 14:36:06 +00:00
$repoConfig [ 'url' ] = 'http://' . $repoConfig [ 'url' ];
2011-12-03 11:43:38 +00:00
}
2012-04-09 14:36:06 +00:00
$repoConfig [ 'url' ] = rtrim ( $repoConfig [ 'url' ], '/' );
2012-10-11 18:54:59 +00:00
if ( 'https?' === substr ( $repoConfig [ 'url' ], 0 , 6 )) {
$repoConfig [ 'url' ] = ( extension_loaded ( 'openssl' ) ? 'https' : 'http' ) . substr ( $repoConfig [ 'url' ], 6 );
}
2012-11-05 14:39:43 +00:00
$urlBits = parse_url ( $repoConfig [ 'url' ]);
2013-10-30 16:46:35 +00:00
if ( $urlBits === false || empty ( $urlBits [ 'scheme' ])) {
2012-04-09 14:36:06 +00:00
throw new \UnexpectedValueException ( 'Invalid url given for Composer repository: ' . $repoConfig [ 'url' ]);
2011-09-20 21:34:06 +00:00
}
2012-10-03 09:56:31 +00:00
if ( ! isset ( $repoConfig [ 'options' ])) {
$repoConfig [ 'options' ] = array ();
}
2013-02-21 16:37:18 +00:00
if ( isset ( $repoConfig [ 'allow_ssl_downgrade' ]) && true === $repoConfig [ 'allow_ssl_downgrade' ]) {
$this -> allowSslDowngrade = true ;
}
2012-10-03 09:56:31 +00:00
2012-04-13 00:58:40 +00:00
$this -> config = $config ;
2012-10-03 09:56:31 +00:00
$this -> options = $repoConfig [ 'options' ];
2012-04-09 14:36:06 +00:00
$this -> url = $repoConfig [ 'url' ];
2018-07-24 07:30:06 +00:00
// force url for packagist.org to repo.packagist.org
2018-07-24 16:20:04 +00:00
if ( preg_match ( '{^(?P<proto>https?)://packagist\.org/?$}i' , $this -> url , $match )) {
2018-07-24 07:30:06 +00:00
$this -> url = $match [ 'proto' ] . '://repo.packagist.org' ;
}
2016-09-17 11:27:29 +00:00
$this -> baseUrl = rtrim ( preg_replace ( '{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}' , '' , $this -> url ), '/' );
2012-03-18 20:04:15 +00:00
$this -> io = $io ;
2019-02-20 12:24:44 +00:00
$this -> cache = new Cache ( $io , $config -> get ( 'cache-repo-dir' ) . '/' . preg_replace ( '{[^a-z0-9.]}i' , '-' , $this -> url ), 'a-z0-9.$~' );
2018-09-12 16:58:54 +00:00
$this -> versionParser = new VersionParser ();
$this -> loader = new ArrayLoader ( $this -> versionParser );
$this -> httpDownloader = $httpDownloader ;
2013-11-19 16:45:28 +00:00
$this -> eventDispatcher = $eventDispatcher ;
2015-11-23 22:18:24 +00:00
$this -> repoConfig = $repoConfig ;
2019-01-17 16:12:33 +00:00
$this -> loop = new Loop ( $this -> httpDownloader );
2015-11-23 22:18:24 +00:00
}
2020-01-29 21:47:16 +00:00
public function getRepoName ()
{
2020-01-30 14:50:46 +00:00
return 'composer repo (' . Url :: sanitize ( $this -> url ) . ')' ;
2020-01-29 21:47:16 +00:00
}
2015-11-23 22:18:24 +00:00
public function getRepoConfig ()
{
return $this -> repoConfig ;
2011-04-17 22:14:44 +00:00
}
2014-05-13 21:54:48 +00:00
/**
* { @ inheritDoc }
*/
2015-06-18 16:44:58 +00:00
public function findPackage ( $name , $constraint )
2013-03-10 12:33:41 +00:00
{
2018-11-12 16:44:18 +00:00
// this call initializes loadRootServerFile which is needed for the rest below to work
$hasProviders = $this -> hasProviders ();
2015-06-18 16:44:58 +00:00
2014-05-13 21:54:48 +00:00
$name = strtolower ( $name );
2015-09-24 14:32:36 +00:00
if ( ! $constraint instanceof ConstraintInterface ) {
2018-09-12 16:58:54 +00:00
$constraint = $this -> versionParser -> parseConstraints ( $constraint );
2015-06-18 16:44:58 +00:00
}
2014-05-13 21:54:48 +00:00
2018-11-12 16:44:18 +00:00
if ( $this -> lazyProvidersUrl ) {
2018-12-04 16:03:56 +00:00
if ( $this -> hasPartialPackages () && isset ( $this -> partialPackagesByName [ $name ])) {
2018-12-04 16:37:39 +00:00
return $this -> filterPackages ( $this -> whatProvides ( $name ), $constraint , true );
2018-12-04 16:03:56 +00:00
}
2019-01-15 10:40:49 +00:00
if ( is_array ( $this -> availablePackages ) && ! isset ( $this -> availablePackages [ $name ])) {
return ;
}
2019-02-20 12:24:44 +00:00
$packages = $this -> loadAsyncPackages ( array ( $name => $constraint ));
2018-12-04 16:03:56 +00:00
2020-01-15 11:02:12 +00:00
return reset ( $packages [ 'packages' ]);
2018-11-12 16:44:18 +00:00
}
2019-01-15 10:40:49 +00:00
if ( $hasProviders ) {
foreach ( $this -> getProviderNames () as $providerName ) {
if ( $name === $providerName ) {
return $this -> filterPackages ( $this -> whatProvides ( $providerName ), $constraint , true );
}
2014-05-13 21:54:48 +00:00
}
2019-01-15 10:40:49 +00:00
return ;
2013-03-10 12:33:41 +00:00
}
2019-01-15 10:40:49 +00:00
return parent :: findPackage ( $name , $constraint );
2013-03-10 12:33:41 +00:00
}
2012-08-22 15:47:05 +00:00
/**
* { @ inheritDoc }
*/
2015-06-18 16:44:58 +00:00
public function findPackages ( $name , $constraint = null )
2012-08-20 13:44:45 +00:00
{
2018-11-12 16:21:23 +00:00
// this call initializes loadRootServerFile which is needed for the rest below to work
$hasProviders = $this -> hasProviders ();
2018-12-04 16:03:56 +00:00
$name = strtolower ( $name );
if ( null !== $constraint && ! $constraint instanceof ConstraintInterface ) {
$constraint = $this -> versionParser -> parseConstraints ( $constraint );
}
2018-11-12 16:21:23 +00:00
if ( $this -> lazyProvidersUrl ) {
2018-12-04 16:03:56 +00:00
if ( $this -> hasPartialPackages () && isset ( $this -> partialPackagesByName [ $name ])) {
2018-12-04 16:37:39 +00:00
return $this -> filterPackages ( $this -> whatProvides ( $name ), $constraint );
2018-12-04 16:03:56 +00:00
}
2019-01-15 10:40:49 +00:00
if ( is_array ( $this -> availablePackages ) && ! isset ( $this -> availablePackages [ $name ])) {
return array ();
}
2020-01-15 11:02:12 +00:00
$result = $this -> loadAsyncPackages ( array ( $name => $constraint ));
return $result [ 'packages' ];
2018-11-12 16:21:23 +00:00
}
2018-12-04 16:03:56 +00:00
2019-01-15 10:40:49 +00:00
if ( $hasProviders ) {
foreach ( $this -> getProviderNames () as $providerName ) {
if ( $name === $providerName ) {
return $this -> filterPackages ( $this -> whatProvides ( $providerName ), $constraint );
}
2018-12-04 16:03:56 +00:00
}
2019-01-15 10:40:49 +00:00
return array ();
2012-08-24 00:29:03 +00:00
}
2012-08-20 13:44:45 +00:00
2019-01-15 10:40:49 +00:00
return parent :: findPackages ( $name , $constraint );
2018-12-04 16:03:56 +00:00
}
2018-12-04 16:37:39 +00:00
private function filterPackages ( array $packages , $constraint = null , $returnFirstMatch = false )
2018-12-04 16:03:56 +00:00
{
2019-01-14 15:37:33 +00:00
if ( null === $constraint ) {
if ( $returnFirstMatch ) {
return reset ( $packages );
}
2012-08-20 13:44:45 +00:00
2019-01-14 15:37:33 +00:00
return $packages ;
}
2018-12-04 16:03:56 +00:00
2019-01-14 15:37:33 +00:00
$filteredPackages = array ();
foreach ( $packages as $package ) {
2018-12-04 16:03:56 +00:00
$pkgConstraint = new Constraint ( '==' , $package -> getVersion ());
2019-01-14 15:37:33 +00:00
if ( $constraint -> matches ( $pkgConstraint )) {
2018-12-04 16:03:56 +00:00
if ( $returnFirstMatch ) {
return $package ;
2014-05-13 21:54:48 +00:00
}
2018-12-04 16:03:56 +00:00
2019-01-14 15:37:33 +00:00
$filteredPackages [] = $package ;
2012-08-20 13:44:45 +00:00
}
2014-05-13 21:54:48 +00:00
}
2012-08-20 13:44:45 +00:00
2018-12-04 16:03:56 +00:00
if ( $returnFirstMatch ) {
return null ;
}
2019-01-14 15:37:33 +00:00
return $filteredPackages ;
2014-05-13 21:54:48 +00:00
}
2012-08-22 12:20:43 +00:00
2013-03-10 12:33:41 +00:00
public function getPackages ()
{
2019-01-15 10:40:49 +00:00
$hasProviders = $this -> hasProviders ();
if ( $this -> lazyProvidersUrl ) {
if ( is_array ( $this -> availablePackages )) {
$packageMap = array ();
foreach ( $this -> availablePackages as $name ) {
$packageMap [ $name ] = new EmptyConstraint ();
}
2020-01-15 11:02:12 +00:00
$result = $this -> loadAsyncPackages ( $packageMap );
return array_values ( $result [ 'packages' ]);
2019-01-15 10:40:49 +00:00
}
2020-01-14 15:20:31 +00:00
if ( $this -> hasPartialPackages ()) {
return array_values ( $this -> partialPackagesByName );
}
throw new \LogicException ( 'Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.' );
2019-01-15 10:40:49 +00:00
}
if ( $hasProviders ) {
2020-01-14 15:20:31 +00:00
throw new \LogicException ( 'Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.' );
2012-08-20 13:44:45 +00:00
}
2013-03-10 12:33:41 +00:00
return parent :: getPackages ();
2012-08-20 13:44:45 +00:00
}
2018-12-04 16:03:56 +00:00
public function getPackageNames ()
{
2019-01-15 10:40:49 +00:00
// TODO add getPackageNames to the RepositoryInterface perhaps? With filtering capability embedded?
$hasProviders = $this -> hasProviders ();
if ( $this -> lazyProvidersUrl ) {
if ( is_array ( $this -> availablePackages )) {
return array_keys ( $this -> availablePackages );
}
// TODO implement new list API endpoint for those repos somehow?
2020-01-14 15:20:31 +00:00
if ( $this -> hasPartialPackages ()) {
return array_keys ( $this -> partialPackagesByName );
}
2019-01-15 10:40:49 +00:00
return array ();
}
if ( $hasProviders ) {
2018-12-04 16:03:56 +00:00
return $this -> getProviderNames ();
}
2019-01-15 10:40:49 +00:00
$names = array ();
foreach ( $this -> getPackages () as $package ) {
$names [] = $package -> getPrettyName ();
}
2018-12-04 16:03:56 +00:00
2019-01-15 10:40:49 +00:00
return $names ;
2018-12-04 16:03:56 +00:00
}
2020-01-16 06:58:50 +00:00
public function loadPackages ( array $packageNameMap , array $acceptableStabilities , array $stabilityFlags )
2018-09-12 12:11:26 +00:00
{
2018-11-12 16:21:23 +00:00
// this call initializes loadRootServerFile which is needed for the rest below to work
$hasProviders = $this -> hasProviders ();
2018-12-04 16:03:56 +00:00
if ( ! $hasProviders && ! $this -> hasPartialPackages () && ! $this -> lazyProvidersUrl ) {
2020-01-16 06:58:50 +00:00
return parent :: loadPackages ( $packageNameMap , $acceptableStabilities , $stabilityFlags );
2018-09-12 12:11:26 +00:00
}
$packages = array ();
2020-01-15 11:02:12 +00:00
$namesFound = array ();
2018-12-04 16:03:56 +00:00
if ( $hasProviders || $this -> hasPartialPackages ()) {
foreach ( $packageNameMap as $name => $constraint ) {
$matches = array ();
2018-12-05 12:28:37 +00:00
// if a repo has no providers but only partial packages and the partial packages are missing
// then we don't want to call whatProvides as it would try to load from the providers and fail
2018-12-04 16:03:56 +00:00
if ( ! $hasProviders && ! isset ( $this -> partialPackagesByName [ $name ])) {
continue ;
2018-09-12 14:32:14 +00:00
}
2018-12-04 16:03:56 +00:00
2020-01-16 06:58:50 +00:00
$candidates = $this -> whatProvides ( $name , $acceptableStabilities , $stabilityFlags );
2018-12-04 16:03:56 +00:00
foreach ( $candidates as $candidate ) {
2018-12-05 12:28:37 +00:00
if ( $candidate -> getName () !== $name ) {
throw new \LogicException ( 'whatProvides should never return a package with a different name than the requested one' );
}
2020-01-15 11:02:12 +00:00
$namesFound [ $name ] = true ;
2018-12-05 12:28:37 +00:00
if ( ! $constraint || $constraint -> matches ( new Constraint ( '==' , $candidate -> getVersion ()))) {
2018-09-12 14:32:14 +00:00
$matches [ spl_object_hash ( $candidate )] = $candidate ;
2018-12-04 16:03:56 +00:00
if ( $candidate instanceof AliasPackage && ! isset ( $matches [ spl_object_hash ( $candidate -> getAliasOf ())])) {
$matches [ spl_object_hash ( $candidate -> getAliasOf ())] = $candidate -> getAliasOf ();
}
2018-09-12 14:32:14 +00:00
}
2018-09-12 12:11:26 +00:00
}
2019-02-18 15:59:09 +00:00
// add aliases of matched packages even if they did not match the constraint
2018-12-04 16:03:56 +00:00
foreach ( $candidates as $candidate ) {
if ( $candidate instanceof AliasPackage ) {
2019-02-18 15:59:09 +00:00
if ( isset ( $matches [ spl_object_hash ( $candidate -> getAliasOf ())])) {
2018-12-04 16:03:56 +00:00
$matches [ spl_object_hash ( $candidate )] = $candidate ;
}
}
}
$packages = array_merge ( $packages , $matches );
unset ( $packageNameMap [ $name ]);
2018-09-12 12:11:26 +00:00
}
2018-12-04 16:03:56 +00:00
}
2018-12-05 12:28:37 +00:00
if ( $this -> lazyProvidersUrl && count ( $packageNameMap )) {
2019-01-15 10:40:49 +00:00
if ( is_array ( $this -> availablePackages )) {
$availPackages = $this -> availablePackages ;
2020-04-22 14:40:28 +00:00
foreach ( $packageNameMap as $name => $constraint ) {
if ( ! isset ( $availPackages [ strtolower ( $name )])) {
unset ( $packageNameMap [ $name ]);
}
}
2019-01-15 10:40:49 +00:00
}
2020-01-16 06:58:50 +00:00
$result = $this -> loadAsyncPackages ( $packageNameMap , $acceptableStabilities , $stabilityFlags );
2020-01-15 11:02:12 +00:00
$packages = array_merge ( $packages , $result [ 'packages' ]);
$namesFound = array_merge ( $namesFound , $result [ 'namesFound' ]);
2018-09-12 12:11:26 +00:00
}
2018-11-12 16:21:23 +00:00
2020-01-15 11:02:12 +00:00
return array ( 'namesFound' => array_keys ( $namesFound ), 'packages' => $packages );
2018-09-12 12:11:26 +00:00
}
2012-08-23 17:16:23 +00:00
/**
* { @ inheritDoc }
*/
2016-06-21 14:38:52 +00:00
public function search ( $query , $mode = 0 , $type = null )
2012-08-23 17:16:23 +00:00
{
2013-03-10 12:32:59 +00:00
$this -> loadRootServerFile ();
if ( $this -> searchUrl && $mode === self :: SEARCH_FULLTEXT ) {
2016-06-21 14:38:52 +00:00
$url = str_replace ( array ( '%query%' , '%type%' ), array ( $query , $type ), $this -> searchUrl );
2013-03-10 12:32:59 +00:00
2018-11-28 07:59:45 +00:00
$search = $this -> httpDownloader -> get ( $url , $this -> options ) -> decodeJson ();
2013-03-10 12:32:59 +00:00
2018-05-15 14:06:56 +00:00
if ( empty ( $search [ 'results' ])) {
return array ();
}
2018-05-15 13:20:18 +00:00
$results = array ();
foreach ( $search [ 'results' ] as $result ) {
// do not show virtual packages in results as they are not directly useful from a composer perspective
if ( empty ( $result [ 'virtual' ])) {
$results [] = $result ;
}
}
return $results ;
2012-08-24 00:29:03 +00:00
}
2012-08-23 17:16:23 +00:00
2019-01-15 10:40:49 +00:00
if ( $this -> hasProviders () || $this -> lazyProvidersUrl ) {
2013-03-10 12:32:59 +00:00
$results = array ();
$regex = '{(?:' . implode ( '|' , preg_split ( '{\s+}' , $query )) . ')}i' ;
2019-01-15 10:40:49 +00:00
foreach ( $this -> getPackageNames () as $name ) {
2013-03-10 12:32:59 +00:00
if ( preg_match ( $regex , $name )) {
$results [] = array ( 'name' => $name );
2012-08-23 17:16:23 +00:00
}
}
2013-03-10 12:32:59 +00:00
return $results ;
}
return parent :: search ( $query , $mode );
}
2020-01-30 08:23:20 +00:00
public function getProviders ( $packageName )
{
2020-03-12 14:30:20 +00:00
$this -> loadRootServerFile ();
$result = array ();
if ( $this -> providersApiUrl ) {
$apiResult = $this -> httpDownloader -> get ( str_replace ( '%package%' , $packageName , $this -> providersApiUrl ), $this -> options ) -> decodeJson ();
foreach ( $apiResult [ 'providers' ] as $provider ) {
$result [ $provider [ 'name' ]] = $provider ;
}
return $result ;
2020-01-30 08:23:20 +00:00
}
2020-03-12 14:30:20 +00:00
if ( $this -> hasPartialPackages ()) {
foreach ( $this -> partialPackagesByName as $versions ) {
foreach ( $versions as $candidate ) {
if ( isset ( $result [ $candidate [ 'name' ]]) || ! isset ( $candidate [ 'provide' ][ $packageName ])) {
continue ;
}
$result [ $candidate [ 'name' ]] = array (
'name' => $candidate [ 'name' ],
'description' => isset ( $candidate [ 'description' ]) ? $candidate [ 'description' ] : '' ,
'type' => isset ( $candidate [ 'type' ]) ? $candidate [ 'type' ] : '' ,
);
}
}
}
if ( $this -> packages ) {
$result = array_merge ( $result , parent :: getProviders ( $packageName ));
}
2020-01-30 08:23:20 +00:00
2020-03-12 14:30:20 +00:00
return $result ;
2020-01-30 08:23:20 +00:00
}
2018-12-04 16:03:56 +00:00
private function getProviderNames ()
2013-03-10 12:32:59 +00:00
{
$this -> loadRootServerFile ();
if ( null === $this -> providerListing ) {
$this -> loadProviderListings ( $this -> loadRootServerFile ());
}
2015-01-31 21:24:33 +00:00
if ( $this -> lazyProvidersUrl ) {
// Can not determine list of provided packages for lazy repositories
return array ();
}
2013-03-10 12:32:59 +00:00
if ( $this -> providersUrl ) {
return array_keys ( $this -> providerListing );
}
2016-02-15 20:37:19 +00:00
return array ();
2012-08-23 17:16:23 +00:00
}
2018-12-04 16:03:56 +00:00
private function configurePackageTransportOptions ( PackageInterface $package )
2012-08-22 15:47:05 +00:00
{
2014-05-07 17:12:58 +00:00
foreach ( $package -> getDistUrls () as $url ) {
if ( strpos ( $url , $this -> baseUrl ) === 0 ) {
$package -> setTransportOptions ( $this -> options );
2012-08-22 15:47:05 +00:00
2014-05-07 17:12:58 +00:00
return ;
}
2013-08-19 07:40:54 +00:00
}
2012-08-22 15:47:05 +00:00
}
2018-12-04 16:03:56 +00:00
private function hasProviders ()
2012-10-01 11:58:01 +00:00
{
$this -> loadRootServerFile ();
2012-10-14 14:33:53 +00:00
return $this -> hasProviders ;
2012-10-01 11:58:01 +00:00
}
2016-03-27 17:39:36 +00:00
/**
2018-09-11 11:33:29 +00:00
* @ param string $name package name
2016-04-06 23:12:30 +00:00
* @ return array | mixed
2016-03-27 17:39:36 +00:00
*/
2020-04-13 10:10:31 +00:00
private function whatProvides ( $name , array $acceptableStabilities = null , array $stabilityFlags = null )
2012-10-01 11:58:01 +00:00
{
2018-12-04 16:03:56 +00:00
if ( ! $this -> hasPartialPackages () || ! isset ( $this -> partialPackagesByName [ $name ])) {
2016-11-30 21:34:59 +00:00
// skip platform packages, root package and composer-plugin-api
2020-04-21 19:57:27 +00:00
if ( preg_match ( PlatformRepository :: PLATFORM_PACKAGE_REGEX , $name ) || '__root__' === $name ) {
2013-02-21 15:50:04 +00:00
return array ();
}
2012-10-14 14:33:53 +00:00
2016-11-30 21:34:59 +00:00
if ( null === $this -> providerListing ) {
$this -> loadProviderListings ( $this -> loadRootServerFile ());
}
2012-10-14 14:33:53 +00:00
2016-11-30 21:34:59 +00:00
$useLastModifiedCheck = false ;
if ( $this -> lazyProvidersUrl && ! isset ( $this -> providerListing [ $name ])) {
$hash = null ;
$url = str_replace ( '%package%' , $name , $this -> lazyProvidersUrl );
$cacheKey = 'provider-' . strtr ( $name , '/' , '$' ) . '.json' ;
$useLastModifiedCheck = true ;
} elseif ( $this -> providersUrl ) {
// package does not exist in this repo
if ( ! isset ( $this -> providerListing [ $name ])) {
return array ();
}
$hash = $this -> providerListing [ $name ][ 'sha256' ];
$url = str_replace ( array ( '%package%' , '%hash%' ), array ( $name , $hash ), $this -> providersUrl );
$cacheKey = 'provider-' . strtr ( $name , '/' , '$' ) . '.json' ;
} else {
return array ();
}
$packages = null ;
if ( $cacheKey ) {
if ( ! $useLastModifiedCheck && $hash && $this -> cache -> sha256 ( $cacheKey ) === $hash ) {
$packages = json_decode ( $this -> cache -> read ( $cacheKey ), true );
} elseif ( $useLastModifiedCheck ) {
if ( $contents = $this -> cache -> read ( $cacheKey )) {
$contents = json_decode ( $contents , true );
if ( isset ( $contents [ 'last-modified' ])) {
$response = $this -> fetchFileIfLastModified ( $url , $cacheKey , $contents [ 'last-modified' ]);
if ( true === $response ) {
$packages = $contents ;
} elseif ( $response ) {
$packages = $response ;
}
2016-01-18 12:25:37 +00:00
}
}
}
}
2016-11-30 21:34:59 +00:00
if ( ! $packages ) {
try {
$packages = $this -> fetchFile ( $url , $cacheKey , $hash , $useLastModifiedCheck );
} catch ( TransportException $e ) {
// 404s are acceptable for lazy provider repos
if ( $e -> getStatusCode () === 404 && $this -> lazyProvidersUrl ) {
$packages = array ( 'packages' => array ());
} else {
throw $e ;
}
2016-01-18 12:25:37 +00:00
}
}
2016-11-30 21:34:59 +00:00
$loadingPartialPackage = false ;
} else {
$packages = array ( 'packages' => array ( 'versions' => $this -> partialPackagesByName [ $name ]));
$loadingPartialPackage = true ;
2012-10-01 11:58:01 +00:00
}
2019-01-14 14:30:55 +00:00
$result = array ();
$versionsToLoad = array ();
2012-10-13 16:54:48 +00:00
foreach ( $packages [ 'packages' ] as $versions ) {
2012-10-01 11:58:01 +00:00
foreach ( $versions as $version ) {
2018-12-04 16:37:39 +00:00
$normalizedName = strtolower ( $version [ 'name' ]);
// only load the actual named package, not other packages that might find themselves in the same file
if ( $normalizedName !== $name ) {
continue ;
}
if ( ! $loadingPartialPackage && $this -> hasPartialPackages () && isset ( $this -> partialPackagesByName [ $normalizedName ])) {
2016-11-30 21:34:59 +00:00
continue ;
}
2019-01-14 14:30:55 +00:00
if ( ! isset ( $versionsToLoad [ $version [ 'uid' ]])) {
2019-02-20 12:24:44 +00:00
if ( ! isset ( $version [ 'version_normalized' ])) {
$version [ 'version_normalized' ] = $this -> versionParser -> normalize ( $version [ 'version' ]);
2020-04-07 13:49:07 +00:00
} elseif ( $version [ 'version_normalized' ] === VersionParser :: DEV_MASTER_ALIAS ) {
// handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEV_MASTER_ALIAS, we renormalize it
2020-03-11 15:11:12 +00:00
$version [ 'version_normalized' ] = $this -> versionParser -> normalize ( $version [ 'version' ]);
2012-10-13 16:54:48 +00:00
}
2020-04-13 10:10:31 +00:00
if ( $this -> isVersionAcceptable ( null , $normalizedName , $version , $acceptableStabilities , $stabilityFlags )) {
2019-02-20 12:24:44 +00:00
$versionsToLoad [ $version [ 'uid' ]] = $version ;
}
2018-12-04 10:20:35 +00:00
}
}
2019-01-14 14:30:55 +00:00
}
2012-10-01 11:58:01 +00:00
2019-01-14 14:30:55 +00:00
// load acceptable packages in the providers
$loadedPackages = $this -> createPackages ( $versionsToLoad , 'Composer\Package\CompletePackage' );
$uids = array_keys ( $versionsToLoad );
2012-10-24 14:11:32 +00:00
2019-01-14 14:30:55 +00:00
foreach ( $loadedPackages as $index => $package ) {
$package -> setRepository ( $this );
$uid = $uids [ $index ];
2012-10-24 14:11:32 +00:00
2019-01-14 14:30:55 +00:00
if ( $package instanceof AliasPackage ) {
$aliased = $package -> getAliasOf ();
$aliased -> setRepository ( $this );
2012-10-24 14:11:32 +00:00
2019-01-14 14:30:55 +00:00
$result [ $uid ] = $aliased ;
$result [ $uid . '-alias' ] = $package ;
} else {
$result [ $uid ] = $package ;
2012-10-01 11:58:01 +00:00
}
}
2016-03-27 17:39:36 +00:00
return $result ;
2012-10-01 11:58:01 +00:00
}
2012-08-22 15:47:05 +00:00
/**
* { @ inheritDoc }
*/
2011-04-17 22:14:44 +00:00
protected function initialize ()
{
parent :: initialize ();
2012-04-06 20:39:43 +00:00
2012-08-20 13:44:45 +00:00
$repoData = $this -> loadDataFromServer ();
2018-12-04 10:20:35 +00:00
foreach ( $this -> createPackages ( $repoData , 'Composer\Package\CompletePackage' ) as $package ) {
$this -> addPackage ( $package );
2012-08-20 13:44:45 +00:00
}
}
2013-08-19 07:40:54 +00:00
/**
* Adds a new package to the repository
*
* @ param PackageInterface $package
*/
public function addPackage ( PackageInterface $package )
{
parent :: addPackage ( $package );
2014-05-07 17:10:55 +00:00
$this -> configurePackageTransportOptions ( $package );
2013-08-19 07:40:54 +00:00
}
2019-02-20 12:24:44 +00:00
/**
* @ param array $packageNames array of package name => ConstraintInterface | null - if a constraint is provided , only packages matching it will be loaded
*/
2020-01-16 06:58:50 +00:00
private function loadAsyncPackages ( array $packageNames , array $acceptableStabilities = null , array $stabilityFlags = null )
2018-09-12 16:58:54 +00:00
{
$this -> loadRootServerFile ();
$packages = array ();
2020-01-15 11:02:12 +00:00
$namesFound = array ();
2019-01-17 16:12:33 +00:00
$promises = array ();
2018-09-12 16:58:54 +00:00
$repo = $this ;
2018-12-04 16:03:56 +00:00
if ( ! $this -> lazyProvidersUrl ) {
throw new \LogicException ( 'loadAsyncPackages only supports v2 protocol composer repos with a metadata-url' );
}
2018-12-04 08:34:32 +00:00
2020-01-16 06:58:50 +00:00
// load ~dev versions of the packages as well if needed
2019-02-20 07:42:49 +00:00
foreach ( $packageNames as $name => $constraint ) {
2020-04-19 13:30:58 +00:00
if ( $acceptableStabilities === null || $stabilityFlags === null || StabilityFilter :: isPackageAcceptable ( $acceptableStabilities , $stabilityFlags , array ( $name ), 'dev' )) {
2020-01-16 06:58:50 +00:00
$packageNames [ $name . '~dev' ] = $constraint ;
}
2019-02-20 07:42:49 +00:00
}
2018-12-04 16:03:56 +00:00
foreach ( $packageNames as $name => $constraint ) {
$name = strtolower ( $name );
2018-11-12 16:21:23 +00:00
2019-02-20 09:51:07 +00:00
$realName = preg_replace ( '{~dev$}' , '' , $name );
2018-12-04 16:03:56 +00:00
// skip platform packages, root package and composer-plugin-api
2020-04-21 19:57:27 +00:00
if ( preg_match ( PlatformRepository :: PLATFORM_PACKAGE_REGEX , $realName ) || '__root__' === $realName ) {
2018-12-04 16:03:56 +00:00
continue ;
}
2018-09-12 16:58:54 +00:00
2018-12-04 16:03:56 +00:00
$url = str_replace ( '%package%' , $name , $this -> lazyProvidersUrl );
2019-02-20 12:24:44 +00:00
$cacheKey = 'provider-' . strtr ( $name , '/' , '~' ) . '.json' ;
2018-09-12 16:58:54 +00:00
2018-12-04 16:03:56 +00:00
$lastModified = null ;
if ( $contents = $this -> cache -> read ( $cacheKey )) {
$contents = json_decode ( $contents , true );
$lastModified = isset ( $contents [ 'last-modified' ]) ? $contents [ 'last-modified' ] : null ;
}
2018-12-04 10:20:35 +00:00
2019-01-17 16:12:33 +00:00
$promises [] = $this -> asyncFetchFile ( $url , $cacheKey , $lastModified )
2020-01-16 06:58:50 +00:00
-> then ( function ( $response ) use ( & $packages , & $namesFound , $contents , $realName , $constraint , $repo , $acceptableStabilities , $stabilityFlags ) {
2018-12-04 16:03:56 +00:00
if ( true === $response ) {
$response = $contents ;
}
2018-10-31 11:44:54 +00:00
2019-02-20 09:51:07 +00:00
if ( ! isset ( $response [ 'packages' ][ $realName ])) {
2018-12-04 16:03:56 +00:00
return ;
}
2019-02-20 09:51:07 +00:00
$versions = $response [ 'packages' ][ $realName ];
2019-01-18 17:49:45 +00:00
if ( isset ( $response [ 'minified' ]) && $response [ 'minified' ] === 'composer/2.0' ) {
2020-01-15 11:58:30 +00:00
$versions = MetadataMinifier :: expand ( $versions );
2019-01-18 17:49:45 +00:00
}
2020-01-15 11:02:12 +00:00
$namesFound [ $realName ] = true ;
2018-12-04 16:03:56 +00:00
$versionsToLoad = array ();
2019-01-18 17:49:45 +00:00
foreach ( $versions as $version ) {
2019-02-20 10:10:44 +00:00
if ( ! isset ( $version [ 'version_normalized' ])) {
$version [ 'version_normalized' ] = $repo -> versionParser -> normalize ( $version [ 'version' ]);
2020-04-07 13:49:07 +00:00
} elseif ( $version [ 'version_normalized' ] === VersionParser :: DEV_MASTER_ALIAS ) {
// handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEV_MASTER_ALIAS, we renormalize it
2020-03-11 15:20:35 +00:00
$version [ 'version_normalized' ] = $repo -> versionParser -> normalize ( $version [ 'version' ]);
2019-02-20 10:10:44 +00:00
}
2018-12-04 10:20:35 +00:00
2020-04-13 10:10:31 +00:00
if ( $repo -> isVersionAcceptable ( $constraint , $realName , $version , $acceptableStabilities , $stabilityFlags )) {
2019-02-20 10:10:44 +00:00
$versionsToLoad [] = $version ;
2018-12-04 10:20:35 +00:00
}
2018-12-04 16:03:56 +00:00
}
2018-12-04 10:20:35 +00:00
2018-12-04 16:03:56 +00:00
$loadedPackages = $repo -> createPackages ( $versionsToLoad , 'Composer\Package\CompletePackage' );
foreach ( $loadedPackages as $package ) {
$package -> setRepository ( $repo );
$packages [ spl_object_hash ( $package )] = $package ;
2019-02-20 12:24:44 +00:00
2018-12-04 16:03:56 +00:00
if ( $package instanceof AliasPackage && ! isset ( $packages [ spl_object_hash ( $package -> getAliasOf ())])) {
2019-02-20 12:24:44 +00:00
$package -> getAliasOf () -> setRepository ( $repo );
2018-12-04 16:03:56 +00:00
$packages [ spl_object_hash ( $package -> getAliasOf ())] = $package -> getAliasOf ();
2018-09-12 16:58:54 +00:00
}
2018-12-04 16:03:56 +00:00
}
});
2018-09-12 16:58:54 +00:00
}
2019-01-17 16:12:33 +00:00
$this -> loop -> wait ( $promises );
2018-09-12 16:58:54 +00:00
2020-01-15 11:02:12 +00:00
return array ( 'namesFound' => $namesFound , 'packages' => $packages );
2018-09-12 16:58:54 +00:00
// RepositorySet should call loadMetadata, getMetadata when all promises resolved, then metadataComplete when done so we can GC the loaded json and whatnot then as needed
}
2018-11-12 16:58:28 +00:00
/**
* TODO v3 should make this private once we can drop PHP 5.3 support
*
2019-02-20 12:24:44 +00:00
* @ param string $name package name ( must be lowercased already )
2018-11-12 16:58:28 +00:00
* @ private
*/
2020-04-13 10:10:31 +00:00
public function isVersionAcceptable ( $constraint , $name , $versionData , array $acceptableStabilities = null , array $stabilityFlags = null )
2018-11-12 16:58:28 +00:00
{
2019-02-20 12:24:44 +00:00
$versions = array ( $versionData [ 'version_normalized' ]);
if ( $alias = $this -> loader -> getBranchAlias ( $versionData )) {
$versions [] = $alias ;
2018-11-12 16:58:28 +00:00
}
2019-02-20 12:24:44 +00:00
foreach ( $versions as $version ) {
2020-01-30 09:17:42 +00:00
if ( null !== $acceptableStabilities && null !== $stabilityFlags && ! StabilityFilter :: isPackageAcceptable ( $acceptableStabilities , $stabilityFlags , array ( $name ), VersionParser :: parseStability ( $version ))) {
2019-02-20 12:24:44 +00:00
continue ;
}
if ( $constraint && ! $constraint -> matches ( new Constraint ( '==' , $version ))) {
continue ;
}
return true ;
2018-11-12 16:58:28 +00:00
}
2019-02-20 12:24:44 +00:00
return false ;
2018-11-12 16:58:28 +00:00
}
2012-10-01 11:58:01 +00:00
protected function loadRootServerFile ()
2012-08-20 13:44:45 +00:00
{
2012-10-01 11:58:01 +00:00
if ( null !== $this -> rootData ) {
return $this -> rootData ;
}
2012-07-20 08:27:02 +00:00
if ( ! extension_loaded ( 'openssl' ) && 'https' === substr ( $this -> url , 0 , 5 )) {
throw new \RuntimeException ( 'You must enable the openssl extension in your php.ini to load information from ' . $this -> url );
}
2012-10-11 19:26:11 +00:00
$jsonUrlParts = parse_url ( $this -> url );
2012-08-29 13:12:08 +00:00
2014-12-08 22:04:10 +00:00
if ( isset ( $jsonUrlParts [ 'path' ]) && false !== strpos ( $jsonUrlParts [ 'path' ], '.json' )) {
2012-10-11 19:26:11 +00:00
$jsonUrl = $this -> url ;
} else {
$jsonUrl = $this -> url . '/packages.json' ;
}
2012-04-13 00:58:40 +00:00
2012-10-11 19:26:11 +00:00
$data = $this -> fetchFile ( $jsonUrl , 'packages.json' );
2012-04-13 00:58:40 +00:00
2013-02-21 15:50:04 +00:00
if ( ! empty ( $data [ 'notify-batch' ])) {
2013-03-10 12:27:49 +00:00
$this -> notifyUrl = $this -> canonicalizeUrl ( $data [ 'notify-batch' ]);
} elseif ( ! empty ( $data [ 'notify' ])) {
$this -> notifyUrl = $this -> canonicalizeUrl ( $data [ 'notify' ]);
2012-11-28 17:44:49 +00:00
}
2013-03-10 12:27:49 +00:00
if ( ! empty ( $data [ 'search' ])) {
$this -> searchUrl = $this -> canonicalizeUrl ( $data [ 'search' ]);
2012-04-06 20:39:43 +00:00
}
2012-04-06 18:40:31 +00:00
2013-07-01 23:45:43 +00:00
if ( ! empty ( $data [ 'mirrors' ])) {
foreach ( $data [ 'mirrors' ] as $mirror ) {
2013-10-19 15:41:29 +00:00
if ( ! empty ( $mirror [ 'git-url' ])) {
$this -> sourceMirrors [ 'git' ][] = array ( 'url' => $mirror [ 'git-url' ], 'preferred' => ! empty ( $mirror [ 'preferred' ]));
}
if ( ! empty ( $mirror [ 'hg-url' ])) {
$this -> sourceMirrors [ 'hg' ][] = array ( 'url' => $mirror [ 'hg-url' ], 'preferred' => ! empty ( $mirror [ 'preferred' ]));
2013-07-01 23:45:43 +00:00
}
if ( ! empty ( $mirror [ 'dist-url' ])) {
2017-05-04 08:47:16 +00:00
$this -> distMirrors [] = array (
'url' => $this -> canonicalizeUrl ( $mirror [ 'dist-url' ]),
2017-05-19 13:14:47 +00:00
'preferred' => ! empty ( $mirror [ 'preferred' ]),
2017-05-04 08:47:16 +00:00
);
2013-07-01 23:45:43 +00:00
}
}
}
2013-07-01 23:46:20 +00:00
if ( ! empty ( $data [ 'providers-lazy-url' ])) {
$this -> lazyProvidersUrl = $this -> canonicalizeUrl ( $data [ 'providers-lazy-url' ]);
$this -> hasProviders = true ;
2016-11-30 21:34:59 +00:00
$this -> hasPartialPackages = ! empty ( $data [ 'packages' ]) && is_array ( $data [ 'packages' ]);
2013-07-01 23:46:20 +00:00
}
2020-04-21 22:43:34 +00:00
// metadata-url indicates V2 repo protocol so it takes over from all the V1 types
2018-12-04 16:03:56 +00:00
// V2 only has lazyProviders and possibly partial packages, but no ability to process anything else,
// V2 also supports async loading
2018-11-29 18:31:41 +00:00
if ( ! empty ( $data [ 'metadata-url' ])) {
$this -> lazyProvidersUrl = $this -> canonicalizeUrl ( $data [ 'metadata-url' ]);
$this -> providersUrl = null ;
$this -> hasProviders = false ;
2018-12-04 16:03:56 +00:00
$this -> hasPartialPackages = ! empty ( $data [ 'packages' ]) && is_array ( $data [ 'packages' ]);
2018-11-29 18:31:41 +00:00
$this -> allowSslDowngrade = false ;
2019-01-15 10:40:49 +00:00
// provides a list of package names that are available in this repo
// this disables lazy-provider behavior in the sense that if a list is available we assume it is finite and won't search for other packages in that repo
// while if no list is there lazyProvidersUrl is used when looking for any package name to see if the repo knows it
if ( ! empty ( $data [ 'available-packages' ])) {
$availPackages = array_map ( 'strtolower' , $data [ 'available-packages' ]);
$this -> availablePackages = array_combine ( $availPackages , $availPackages );
}
// Remove legacy keys as most repos need to be compatible with Composer v1
// as well but we are not interested in the old format anymore at this point
2018-11-29 18:31:41 +00:00
unset ( $data [ 'providers-url' ], $data [ 'providers' ], $data [ 'providers-includes' ]);
}
2013-02-27 12:32:21 +00:00
if ( $this -> allowSslDowngrade ) {
$this -> url = str_replace ( 'https://' , 'http://' , $this -> url );
2015-06-10 16:13:56 +00:00
$this -> baseUrl = str_replace ( 'https://' , 'http://' , $this -> baseUrl );
2013-02-27 12:32:21 +00:00
}
2013-02-21 15:50:04 +00:00
if ( ! empty ( $data [ 'providers-url' ])) {
2013-03-10 12:27:49 +00:00
$this -> providersUrl = $this -> canonicalizeUrl ( $data [ 'providers-url' ]);
2013-02-21 15:50:04 +00:00
$this -> hasProviders = true ;
}
2012-10-14 14:33:53 +00:00
if ( ! empty ( $data [ 'providers' ]) || ! empty ( $data [ 'providers-includes' ])) {
$this -> hasProviders = true ;
2012-10-01 11:58:01 +00:00
}
2020-01-30 08:23:20 +00:00
if ( ! empty ( $data [ 'providers-api' ])) {
2020-04-07 11:09:22 +00:00
$this -> providersApiUrl = $this -> canonicalizeUrl ( $data [ 'providers-api' ]);
2020-01-30 08:23:20 +00:00
}
2012-10-01 11:58:01 +00:00
return $this -> rootData = $data ;
}
2018-12-04 16:03:56 +00:00
private function canonicalizeUrl ( $url )
2013-03-10 12:27:49 +00:00
{
if ( '/' === $url [ 0 ]) {
2019-06-07 07:13:11 +00:00
if ( preg_match ( '{^[^:]++://[^/]*+}' , $this -> url , $matches )) {
2019-05-10 11:55:31 +00:00
return $matches [ 0 ] . $url ;
}
return $this -> url ;
2013-03-10 12:27:49 +00:00
}
return $url ;
}
2018-12-04 16:03:56 +00:00
private function loadDataFromServer ()
2012-10-01 11:58:01 +00:00
{
$data = $this -> loadRootServerFile ();
2012-08-20 13:44:45 +00:00
return $this -> loadIncludes ( $data );
2012-04-06 18:40:31 +00:00
}
2018-12-04 16:03:56 +00:00
private function hasPartialPackages ()
{
if ( $this -> hasPartialPackages && null === $this -> partialPackagesByName ) {
$this -> initializePartialPackages ();
}
return $this -> hasPartialPackages ;
}
private function loadProviderListings ( $data )
2012-10-14 14:33:53 +00:00
{
if ( isset ( $data [ 'providers' ])) {
if ( ! is_array ( $this -> providerListing )) {
$this -> providerListing = array ();
}
$this -> providerListing = array_merge ( $this -> providerListing , $data [ 'providers' ]);
}
2013-02-21 15:50:04 +00:00
if ( $this -> providersUrl && isset ( $data [ 'provider-includes' ])) {
$includes = $data [ 'provider-includes' ];
2013-02-21 17:51:22 +00:00
foreach ( $includes as $include => $metadata ) {
$url = $this -> baseUrl . '/' . str_replace ( '%hash%' , $metadata [ 'sha256' ], $include );
$cacheKey = str_replace ( array ( '%hash%' , '$' ), '' , $include );
if ( $this -> cache -> sha256 ( $cacheKey ) === $metadata [ 'sha256' ]) {
$includedData = json_decode ( $this -> cache -> read ( $cacheKey ), true );
} else {
$includedData = $this -> fetchFile ( $url , $cacheKey , $metadata [ 'sha256' ]);
}
2012-10-14 14:33:53 +00:00
$this -> loadProviderListings ( $includedData );
}
}
}
2018-12-04 16:03:56 +00:00
private function loadIncludes ( $data )
2012-04-06 18:40:31 +00:00
{
2012-08-20 13:44:45 +00:00
$packages = array ();
2012-04-06 18:40:31 +00:00
// legacy repo handling
if ( ! isset ( $data [ 'packages' ]) && ! isset ( $data [ 'includes' ])) {
foreach ( $data as $pkg ) {
foreach ( $pkg [ 'versions' ] as $metadata ) {
2012-08-20 13:44:45 +00:00
$packages [] = $metadata ;
2012-04-06 18:40:31 +00:00
}
}
2012-10-22 20:40:32 +00:00
return $packages ;
2011-04-17 22:14:44 +00:00
}
2012-04-06 18:40:31 +00:00
if ( isset ( $data [ 'packages' ])) {
foreach ( $data [ 'packages' ] as $package => $versions ) {
foreach ( $versions as $version => $metadata ) {
2012-08-20 13:44:45 +00:00
$packages [] = $metadata ;
2012-04-06 18:40:31 +00:00
}
}
2012-04-06 17:56:34 +00:00
}
2012-04-06 18:40:31 +00:00
if ( isset ( $data [ 'includes' ])) {
foreach ( $data [ 'includes' ] as $include => $metadata ) {
2012-04-06 20:39:43 +00:00
if ( $this -> cache -> sha1 ( $include ) === $metadata [ 'sha1' ]) {
$includedData = json_decode ( $this -> cache -> read ( $include ), true );
} else {
2012-10-11 19:26:11 +00:00
$includedData = $this -> fetchFile ( $include );
2012-04-06 20:39:43 +00:00
}
2012-08-20 13:44:45 +00:00
$packages = array_merge ( $packages , $this -> loadIncludes ( $includedData ));
2011-10-29 05:43:26 +00:00
}
2011-04-23 18:52:37 +00:00
}
2012-08-20 13:44:45 +00:00
return $packages ;
2011-04-23 18:52:37 +00:00
}
2012-09-08 00:00:02 +00:00
2018-12-04 10:20:35 +00:00
/**
* TODO v3 should make this private once we can drop PHP 5.3 support
*
* @ private
*/
public function createPackages ( array $packages , $class = 'Composer\Package\CompletePackage' )
2012-09-08 00:00:02 +00:00
{
2018-12-04 10:20:35 +00:00
if ( ! $packages ) {
2018-12-04 16:37:39 +00:00
return array ();
2018-12-04 10:20:35 +00:00
}
2012-09-08 00:00:02 +00:00
try {
2018-12-04 10:20:35 +00:00
foreach ( $packages as & $data ) {
if ( ! isset ( $data [ 'notification-url' ])) {
$data [ 'notification-url' ] = $this -> notifyUrl ;
}
2013-05-14 13:24:18 +00:00
}
2012-11-29 08:24:28 +00:00
2018-12-04 10:20:35 +00:00
$packages = $this -> loader -> loadPackages ( $packages , $class );
foreach ( $packages as $package ) {
if ( isset ( $this -> sourceMirrors [ $package -> getSourceType ()])) {
$package -> setSourceMirrors ( $this -> sourceMirrors [ $package -> getSourceType ()]);
}
$package -> setDistMirrors ( $this -> distMirrors );
$this -> configurePackageTransportOptions ( $package );
2013-10-19 15:41:29 +00:00
}
2014-05-07 17:10:55 +00:00
2018-12-04 10:20:35 +00:00
return $packages ;
2012-09-08 00:00:02 +00:00
} catch ( \Exception $e ) {
2018-12-04 10:20:35 +00:00
throw new \RuntimeException ( 'Could not load packages ' . ( isset ( $packages [ 0 ][ 'name' ]) ? $packages [ 0 ][ 'name' ] : json_encode ( $packages )) . ' in ' . $this -> url . ': [' . get_class ( $e ) . '] ' . $e -> getMessage (), 0 , $e );
2012-09-08 00:00:02 +00:00
}
}
2012-10-11 19:26:11 +00:00
2016-01-18 12:25:37 +00:00
protected function fetchFile ( $filename , $cacheKey = null , $sha256 = null , $storeLastModifiedTime = false )
2012-10-11 19:26:11 +00:00
{
2013-07-01 23:46:20 +00:00
if ( null === $cacheKey ) {
2012-10-11 19:26:11 +00:00
$cacheKey = $filename ;
2012-10-14 14:33:53 +00:00
$filename = $this -> baseUrl . '/' . $filename ;
2012-10-11 19:26:11 +00:00
}
2015-09-20 17:26:23 +00:00
// url-encode $ signs in URLs as bad proxies choke on them
2020-05-03 17:38:31 +00:00
if (( $pos = strpos ( $filename , '$' )) && preg_match ( '{^https?://}i' , $filename )) {
2015-09-28 09:51:14 +00:00
$filename = substr ( $filename , 0 , $pos ) . '%24' . substr ( $filename , $pos + 1 );
2015-09-20 17:26:23 +00:00
}
2012-10-11 19:26:11 +00:00
$retries = 3 ;
while ( $retries -- ) {
try {
2018-11-12 14:34:54 +00:00
if ( $this -> eventDispatcher ) {
$preFileDownloadEvent = new PreFileDownloadEvent ( PluginEvents :: PRE_FILE_DOWNLOAD , $this -> httpDownloader , $filename );
$this -> eventDispatcher -> dispatch ( $preFileDownloadEvent -> getName (), $preFileDownloadEvent );
}
2016-06-21 14:38:52 +00:00
2019-01-18 11:14:37 +00:00
$response = $this -> httpDownloader -> get ( $filename , $this -> options );
2018-09-12 16:58:54 +00:00
$json = $response -> getBody ();
2013-02-21 15:50:04 +00:00
if ( $sha256 && $sha256 !== hash ( 'sha256' , $json )) {
2016-06-24 13:58:32 +00:00
// undo downgrade before trying again if http seems to be hijacked or modifying content somehow
if ( $this -> allowSslDowngrade ) {
$this -> url = str_replace ( 'http://' , 'https://' , $this -> url );
$this -> baseUrl = str_replace ( 'http://' , 'https://' , $this -> baseUrl );
$filename = str_replace ( 'http://' , 'https://' , $filename );
}
2012-10-21 14:10:47 +00:00
if ( $retries ) {
2013-05-03 10:29:18 +00:00
usleep ( 100000 );
2012-10-21 14:10:47 +00:00
continue ;
}
2013-02-21 16:07:53 +00:00
// TODO use scarier wording once we know for sure it doesn't do false positives anymore
2018-01-16 14:13:36 +00:00
throw new RepositorySecurityException ( 'The contents of ' . $filename . ' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.' );
2012-10-15 12:37:27 +00:00
}
2016-01-18 12:25:37 +00:00
2018-09-12 16:58:54 +00:00
$data = $response -> decodeJson ();
2019-02-21 13:49:06 +00:00
HttpDownloader :: outputWarnings ( $this -> io , $this -> url , $data );
2017-03-08 13:10:50 +00:00
2013-07-01 23:46:20 +00:00
if ( $cacheKey ) {
2016-01-18 12:25:37 +00:00
if ( $storeLastModifiedTime ) {
2018-09-12 16:58:54 +00:00
$lastModifiedDate = $response -> getHeader ( 'last-modified' );
2016-01-18 12:25:37 +00:00
if ( $lastModifiedDate ) {
$data [ 'last-modified' ] = $lastModifiedDate ;
$json = json_encode ( $data );
}
}
2013-07-01 23:46:20 +00:00
$this -> cache -> write ( $cacheKey , $json );
}
2012-10-11 19:26:11 +00:00
2018-09-12 16:58:54 +00:00
$response -> collect ();
2012-10-11 19:26:11 +00:00
break ;
} catch ( \Exception $e ) {
2018-09-12 16:58:54 +00:00
if ( $e instanceof \LogicException ) {
throw $e ;
}
2016-01-18 12:25:37 +00:00
if ( $e instanceof TransportException && $e -> getStatusCode () === 404 ) {
throw $e ;
}
2013-02-21 17:16:54 +00:00
if ( $retries ) {
2013-05-03 10:29:18 +00:00
usleep ( 100000 );
2013-02-21 17:16:54 +00:00
continue ;
}
2013-02-21 16:41:38 +00:00
if ( $e instanceof RepositorySecurityException ) {
throw $e ;
}
2013-07-01 23:46:20 +00:00
if ( $cacheKey && ( $contents = $this -> cache -> read ( $cacheKey ))) {
2013-02-21 17:16:54 +00:00
if ( ! $this -> degradedMode ) {
2020-04-08 15:53:15 +00:00
$this -> io -> writeError ( '<warning>' . $this -> url . ' could not be fully loaded (' . $e -> getMessage () . '), package information was loaded from the local cache and may be out of date</warning>' );
2012-10-11 19:26:11 +00:00
}
2013-02-21 17:16:54 +00:00
$this -> degradedMode = true ;
$data = JsonFile :: parseJson ( $contents , $this -> cache -> getRoot () . $cacheKey );
2012-10-11 19:35:51 +00:00
2013-02-21 17:16:54 +00:00
break ;
2012-10-11 19:26:11 +00:00
}
2013-02-21 17:16:54 +00:00
throw $e ;
2012-10-11 19:26:11 +00:00
}
}
2020-04-07 12:13:50 +00:00
if ( ! isset ( $data )) {
throw new \LogicException ( " ComposerRepository: Undefined \$ data. Please report at https://github.com/composer/composer/issues/new. " );
}
2012-10-11 19:26:11 +00:00
return $data ;
}
2016-01-18 12:25:37 +00:00
2018-12-04 16:03:56 +00:00
private function fetchFileIfLastModified ( $filename , $cacheKey , $lastModifiedTime )
2016-01-18 12:25:37 +00:00
{
$retries = 3 ;
while ( $retries -- ) {
try {
2018-11-12 14:34:54 +00:00
if ( $this -> eventDispatcher ) {
$preFileDownloadEvent = new PreFileDownloadEvent ( PluginEvents :: PRE_FILE_DOWNLOAD , $this -> httpDownloader , $filename );
$this -> eventDispatcher -> dispatch ( $preFileDownloadEvent -> getName (), $preFileDownloadEvent );
}
2016-01-18 12:25:37 +00:00
2018-11-28 07:59:45 +00:00
$options = $this -> options ;
if ( isset ( $options [ 'http' ][ 'header' ])) {
$options [ 'http' ][ 'header' ] = ( array ) $options [ 'http' ][ 'header' ];
}
$options [ 'http' ][ 'header' ][] = array ( 'If-Modified-Since: ' . $lastModifiedTime );
2019-01-18 11:14:37 +00:00
$response = $this -> httpDownloader -> get ( $filename , $options );
2018-09-12 16:58:54 +00:00
$json = $response -> getBody ();
if ( $json === '' && $response -> getStatusCode () === 304 ) {
2016-01-18 12:25:37 +00:00
return true ;
}
2018-09-12 16:58:54 +00:00
$data = $response -> decodeJson ();
2019-02-21 13:49:06 +00:00
HttpDownloader :: outputWarnings ( $this -> io , $this -> url , $data );
2017-03-08 13:10:50 +00:00
2018-09-12 16:58:54 +00:00
$lastModifiedDate = $response -> getHeader ( 'last-modified' );
$response -> collect ();
2016-01-18 12:25:37 +00:00
if ( $lastModifiedDate ) {
$data [ 'last-modified' ] = $lastModifiedDate ;
$json = json_encode ( $data );
}
$this -> cache -> write ( $cacheKey , $json );
return $data ;
} catch ( \Exception $e ) {
2018-09-12 16:58:54 +00:00
if ( $e instanceof \LogicException ) {
throw $e ;
}
2016-01-18 12:25:37 +00:00
if ( $e instanceof TransportException && $e -> getStatusCode () === 404 ) {
throw $e ;
}
if ( $retries ) {
usleep ( 100000 );
continue ;
}
if ( ! $this -> degradedMode ) {
2020-04-08 15:53:15 +00:00
$this -> io -> writeError ( '<warning>' . $this -> url . ' could not be fully loaded (' . $e -> getMessage () . '), package information was loaded from the local cache and may be out of date</warning>' );
2016-01-18 12:25:37 +00:00
}
$this -> degradedMode = true ;
2016-01-26 13:07:05 +00:00
2016-01-18 12:25:37 +00:00
return true ;
}
}
}
2016-11-30 21:34:59 +00:00
2018-12-04 16:03:56 +00:00
private function asyncFetchFile ( $filename , $cacheKey , $lastModifiedTime = null )
2018-09-12 16:58:54 +00:00
{
$retries = 3 ;
2018-11-12 14:34:54 +00:00
2020-01-30 07:40:35 +00:00
if ( isset ( $this -> packagesNotFoundCache [ $filename ])) {
2020-01-30 16:30:38 +00:00
return new Promise ( function ( $resolve , $reject ) { $resolve ( array ( 'packages' => array ())); });
2020-01-30 07:40:35 +00:00
}
2020-05-04 19:58:33 +00:00
if ( isset ( $this -> freshMetadataUrls [ $filename ]) && $lastModifiedTime ) {
// make it look like we got a 304 response
return new Promise ( function ( $resolve , $reject ) { $resolve ( true ); });
}
2019-01-18 11:14:37 +00:00
$httpDownloader = $this -> httpDownloader ;
2018-11-12 14:34:54 +00:00
if ( $this -> eventDispatcher ) {
$preFileDownloadEvent = new PreFileDownloadEvent ( PluginEvents :: PRE_FILE_DOWNLOAD , $this -> httpDownloader , $filename );
$this -> eventDispatcher -> dispatch ( $preFileDownloadEvent -> getName (), $preFileDownloadEvent );
}
2018-09-12 16:58:54 +00:00
$options = $lastModifiedTime ? array ( 'http' => array ( 'header' => array ( 'If-Modified-Since: ' . $lastModifiedTime ))) : array ();
$io = $this -> io ;
$url = $this -> url ;
$cache = $this -> cache ;
$degradedMode =& $this -> degradedMode ;
2020-01-30 13:17:49 +00:00
$repo = $this ;
2018-09-12 16:58:54 +00:00
2020-01-30 13:17:49 +00:00
$accept = function ( $response ) use ( $io , $url , $filename , $cache , $cacheKey , $repo ) {
2018-10-31 11:44:54 +00:00
// package not found is acceptable for a v2 protocol repository
if ( $response -> getStatusCode () === 404 ) {
2020-01-30 13:17:49 +00:00
$repo -> packagesNotFoundCache [ $filename ] = true ;
2018-10-31 11:44:54 +00:00
return array ( 'packages' => array ());
}
2018-09-12 16:58:54 +00:00
$json = $response -> getBody ();
if ( $json === '' && $response -> getStatusCode () === 304 ) {
2020-05-04 19:58:33 +00:00
$repo -> freshMetadataUrls [ $filename ] = true ;
2018-09-12 16:58:54 +00:00
return true ;
}
$data = $response -> decodeJson ();
2019-02-21 13:49:06 +00:00
HttpDownloader :: outputWarnings ( $io , $url , $data );
2018-09-12 16:58:54 +00:00
$lastModifiedDate = $response -> getHeader ( 'last-modified' );
$response -> collect ();
if ( $lastModifiedDate ) {
$data [ 'last-modified' ] = $lastModifiedDate ;
$json = JsonFile :: encode ( $data , JsonFile :: JSON_UNESCAPED_SLASHES | JsonFile :: JSON_UNESCAPED_UNICODE );
}
$cache -> write ( $cacheKey , $json );
2020-05-04 19:58:33 +00:00
$repo -> freshMetadataUrls [ $filename ] = true ;
2018-09-12 16:58:54 +00:00
return $data ;
};
2020-01-30 13:17:49 +00:00
$reject = function ( $e ) use ( & $retries , $httpDownloader , $filename , $options , & $reject , $accept , $io , $url , & $degradedMode , $repo ) {
2018-09-12 16:58:54 +00:00
if ( $e instanceof TransportException && $e -> getStatusCode () === 404 ) {
2020-01-30 13:17:49 +00:00
$repo -> packagesNotFoundCache [ $filename ] = true ;
2018-09-12 16:58:54 +00:00
return false ;
}
2018-12-04 10:36:15 +00:00
// special error code returned when network is being artificially disabled
if ( $e instanceof TransportException && $e -> getStatusCode () === 499 ) {
$retries = 0 ;
}
if ( -- $retries > 0 ) {
2018-09-12 16:58:54 +00:00
usleep ( 100000 );
return $httpDownloader -> add ( $filename , $options ) -> then ( $accept , $reject );
}
if ( ! $degradedMode ) {
2020-04-08 15:53:15 +00:00
$io -> writeError ( '<warning>' . $url . ' could not be fully loaded (' . $e -> getMessage () . '), package information was loaded from the local cache and may be out of date</warning>' );
2018-09-12 16:58:54 +00:00
}
$degradedMode = true ;
2019-11-14 08:57:59 +00:00
// special error code returned when network is being artificially disabled
if ( $e instanceof TransportException && $e -> getStatusCode () === 499 ) {
return $accept ( new Response ( array ( 'url' => $url ), 404 , array (), '' ));
}
2019-01-17 16:12:33 +00:00
throw $e ;
2018-09-12 16:58:54 +00:00
};
return $httpDownloader -> add ( $filename , $options ) -> then ( $accept , $reject );
}
2016-11-30 21:34:59 +00:00
/**
* This initializes the packages key of a partial packages . json that contain some packages inlined + a providers - lazy - url
*
* This should only be called once
*/
private function initializePartialPackages ()
{
$rootData = $this -> loadRootServerFile ();
$this -> partialPackagesByName = array ();
foreach ( $rootData [ 'packages' ] as $package => $versions ) {
2016-12-06 20:56:09 +00:00
foreach ( $versions as $version ) {
2018-12-04 16:37:39 +00:00
$this -> partialPackagesByName [ strtolower ( $version [ 'name' ])][] = $version ;
2016-12-06 20:56:09 +00:00
}
2016-11-30 21:34:59 +00:00
}
// wipe rootData as it is fully consumed at this point and this saves some memory
$this -> rootData = true ;
}
2011-09-17 11:39:37 +00:00
}