2012-10-18 14:39:47 +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\Config ;
use Composer\Json\JsonFile ;
2012-11-14 17:03:11 +00:00
use Composer\Json\JsonManipulator ;
2020-11-22 13:26:07 +00:00
use Composer\Json\JsonValidationException ;
2021-05-04 11:25:52 +00:00
use Composer\Util\Filesystem ;
2016-01-25 22:37:54 +00:00
use Composer\Util\Silencer ;
2012-10-18 14:39:47 +00:00
/**
2012-11-14 17:03:11 +00:00
* JSON Configuration Source
*
2012-10-18 14:39:47 +00:00
* @ author Jordi Boggiano < j . boggiano @ seld . be >
2012-11-14 17:03:11 +00:00
* @ author Beau Simensen < beau @ dflydev . com >
2012-10-18 14:39:47 +00:00
*/
class JsonConfigSource implements ConfigSourceInterface
{
2014-02-18 09:38:13 +00:00
/**
2016-02-09 08:20:25 +00:00
* @ var JsonFile
2014-02-18 09:38:13 +00:00
*/
2012-10-18 14:39:47 +00:00
private $file ;
2014-05-27 11:50:47 +00:00
/**
* @ var bool
*/
private $authConfig ;
2012-11-14 17:03:11 +00:00
/**
* Constructor
*
* @ param JsonFile $file
2014-07-16 13:17:38 +00:00
* @ param bool $authConfig
2012-11-14 17:03:11 +00:00
*/
2014-05-27 11:50:47 +00:00
public function __construct ( JsonFile $file , $authConfig = false )
2012-10-18 14:39:47 +00:00
{
$this -> file = $file ;
2014-05-27 11:50:47 +00:00
$this -> authConfig = $authConfig ;
}
/**
* { @ inheritdoc }
*/
public function getName ()
{
return $this -> file -> getPath ();
2012-10-18 14:39:47 +00:00
}
2012-11-14 17:03:11 +00:00
/**
* { @ inheritdoc }
*/
2020-10-30 13:13:50 +00:00
public function addRepository ( $name , $config , $append = true )
2012-10-18 14:39:47 +00:00
{
2020-10-30 13:13:50 +00:00
$this -> manipulateJson ( 'addRepository' , $name , $config , $append , function ( & $config , $repo , $repoConfig ) use ( $append ) {
2017-03-17 21:09:51 +00:00
// if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have
// to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation
if ( isset ( $config [ 'repositories' ])) {
foreach ( $config [ 'repositories' ] as $index => $val ) {
if ( $index === $repo ) {
continue ;
}
if ( is_numeric ( $index ) && ( $val === array ( 'packagist' => false ) || $val === array ( 'packagist.org' => false ))) {
unset ( $config [ 'repositories' ][ $index ]);
$config [ 'repositories' ][ 'packagist.org' ] = false ;
break ;
}
}
}
2020-10-30 13:13:50 +00:00
if ( $append ) {
$config [ 'repositories' ][ $repo ] = $repoConfig ;
} else {
$config [ 'repositories' ] = array ( $repo => $repoConfig ) + $config [ 'repositories' ];
}
2012-10-18 14:39:47 +00:00
});
}
2012-11-14 17:03:11 +00:00
/**
* { @ inheritdoc }
*/
2012-10-18 14:39:47 +00:00
public function removeRepository ( $name )
{
2012-10-18 16:43:31 +00:00
$this -> manipulateJson ( 'removeRepository' , $name , function ( & $config , $repo ) {
2012-10-18 14:39:47 +00:00
unset ( $config [ 'repositories' ][ $repo ]);
});
}
2012-11-14 17:03:11 +00:00
/**
* { @ inheritdoc }
*/
2012-10-18 14:39:47 +00:00
public function addConfigSetting ( $name , $value )
{
2016-02-29 14:17:24 +00:00
$authConfig = $this -> authConfig ;
$this -> manipulateJson ( 'addConfigSetting' , $name , $value , function ( & $config , $key , $val ) use ( $authConfig ) {
2020-03-10 12:39:22 +00:00
if ( preg_match ( '{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}' , $key )) {
2014-05-27 11:50:47 +00:00
list ( $key , $host ) = explode ( '.' , $key , 2 );
2016-02-29 14:17:24 +00:00
if ( $authConfig ) {
2014-05-27 11:50:47 +00:00
$config [ $key ][ $host ] = $val ;
} else {
$config [ 'config' ][ $key ][ $host ] = $val ;
}
} else {
$config [ 'config' ][ $key ] = $val ;
}
2012-10-18 14:39:47 +00:00
});
}
2012-11-14 17:03:11 +00:00
/**
* { @ inheritdoc }
*/
2012-10-18 14:39:47 +00:00
public function removeConfigSetting ( $name )
{
2016-02-29 14:17:24 +00:00
$authConfig = $this -> authConfig ;
$this -> manipulateJson ( 'removeConfigSetting' , $name , function ( & $config , $key ) use ( $authConfig ) {
2020-03-10 12:39:22 +00:00
if ( preg_match ( '{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}' , $key )) {
2014-05-27 11:50:47 +00:00
list ( $key , $host ) = explode ( '.' , $key , 2 );
2016-02-29 14:17:24 +00:00
if ( $authConfig ) {
2014-05-27 11:50:47 +00:00
unset ( $config [ $key ][ $host ]);
} else {
unset ( $config [ 'config' ][ $key ][ $host ]);
}
} else {
unset ( $config [ 'config' ][ $key ]);
}
2012-10-18 14:39:47 +00:00
});
}
2016-04-27 09:48:21 +00:00
/**
* { @ inheritdoc }
*/
public function addProperty ( $name , $value )
{
$this -> manipulateJson ( 'addProperty' , $name , $value , function ( & $config , $key , $val ) {
2020-09-07 18:06:55 +00:00
if ( strpos ( $key , 'extra.' ) === 0 || strpos ( $key , 'scripts.' ) === 0 ) {
2016-04-27 09:48:21 +00:00
$bits = explode ( '.' , $key );
$last = array_pop ( $bits );
2018-04-13 11:10:22 +00:00
$arr = & $config [ reset ( $bits )];
2016-04-27 09:48:21 +00:00
foreach ( $bits as $bit ) {
if ( ! isset ( $arr [ $bit ])) {
$arr [ $bit ] = array ();
}
2017-03-08 14:07:29 +00:00
$arr = & $arr [ $bit ];
2016-04-27 09:48:21 +00:00
}
$arr [ $last ] = $val ;
} else {
$config [ $key ] = $val ;
}
});
}
/**
* { @ inheritdoc }
*/
public function removeProperty ( $name )
{
$this -> manipulateJson ( 'removeProperty' , $name , function ( & $config , $key ) {
2020-09-07 18:06:55 +00:00
if ( strpos ( $key , 'extra.' ) === 0 || strpos ( $key , 'scripts.' ) === 0 ) {
2016-04-27 09:48:21 +00:00
$bits = explode ( '.' , $key );
$last = array_pop ( $bits );
2018-04-13 11:10:22 +00:00
$arr = & $config [ reset ( $bits )];
2016-04-27 09:48:21 +00:00
foreach ( $bits as $bit ) {
if ( ! isset ( $arr [ $bit ])) {
return ;
}
2017-03-08 14:07:29 +00:00
$arr = & $arr [ $bit ];
2016-04-27 09:48:21 +00:00
}
unset ( $arr [ $last ]);
} else {
unset ( $config [ $key ]);
}
});
}
2012-11-14 17:03:11 +00:00
/**
* { @ inheritdoc }
*/
public function addLink ( $type , $name , $value )
{
2013-05-06 09:31:22 +00:00
$this -> manipulateJson ( 'addLink' , $type , $name , $value , function ( & $config , $type , $name , $value ) {
2012-11-14 17:03:11 +00:00
$config [ $type ][ $name ] = $value ;
});
}
/**
* { @ inheritdoc }
*/
public function removeLink ( $type , $name )
{
2013-05-06 09:31:22 +00:00
$this -> manipulateJson ( 'removeSubNode' , $type , $name , function ( & $config , $type , $name ) {
2012-11-14 17:03:11 +00:00
unset ( $config [ $type ][ $name ]);
2020-11-12 10:09:15 +00:00
});
$this -> manipulateJson ( 'removeMainKeyIfEmpty' , $type , function ( & $config , $type ) {
2019-01-28 15:46:58 +00:00
if ( 0 === count ( $config [ $type ])) {
unset ( $config [ $type ]);
}
2012-11-14 17:03:11 +00:00
});
}
2012-10-18 14:39:47 +00:00
protected function manipulateJson ( $method , $args , $fallback )
{
$args = func_get_args ();
// remove method & fallback
array_shift ( $args );
$fallback = array_pop ( $args );
2012-10-18 16:18:40 +00:00
if ( $this -> file -> exists ()) {
2016-02-09 08:20:25 +00:00
if ( ! is_writable ( $this -> file -> getPath ())) {
throw new \RuntimeException ( sprintf ( 'The file "%s" is not writable.' , $this -> file -> getPath ()));
}
2021-05-04 11:25:52 +00:00
if ( ! Filesystem :: isReadable ( $this -> file -> getPath ())) {
2016-02-09 08:20:25 +00:00
throw new \RuntimeException ( sprintf ( 'The file "%s" is not readable.' , $this -> file -> getPath ()));
}
2012-10-18 16:18:40 +00:00
$contents = file_get_contents ( $this -> file -> getPath ());
2014-05-27 11:50:47 +00:00
} elseif ( $this -> authConfig ) {
$contents = " { \n } \n " ;
2012-10-18 16:18:40 +00:00
} else {
$contents = " { \n \" config \" : { \n } \n } \n " ;
}
2014-05-27 11:50:47 +00:00
2012-10-18 14:39:47 +00:00
$manipulator = new JsonManipulator ( $contents );
2012-10-21 16:01:53 +00:00
$newFile = ! $this -> file -> exists ();
2014-05-27 11:50:47 +00:00
// override manipulator method for auth config files
if ( $this -> authConfig && $method === 'addConfigSetting' ) {
$method = 'addSubNode' ;
list ( $mainNode , $name ) = explode ( '.' , $args [ 0 ], 2 );
$args = array ( $mainNode , $name , $args [ 1 ]);
} elseif ( $this -> authConfig && $method === 'removeConfigSetting' ) {
$method = 'removeSubNode' ;
list ( $mainNode , $name ) = explode ( '.' , $args [ 0 ], 2 );
$args = array ( $mainNode , $name );
}
2012-10-18 14:39:47 +00:00
// try to update cleanly
if ( call_user_func_array ( array ( $manipulator , $method ), $args )) {
file_put_contents ( $this -> file -> getPath (), $manipulator -> getContents ());
} else {
// on failed clean update, call the fallback and rewrite the whole file
$config = $this -> file -> read ();
2014-05-16 01:48:30 +00:00
$this -> arrayUnshiftRef ( $args , $config );
2012-10-18 14:39:47 +00:00
call_user_func_array ( $fallback , $args );
2020-02-14 08:33:53 +00:00
// avoid ending up with arrays for keys that should be objects
2020-11-22 13:48:56 +00:00
foreach ( array ( 'require' , 'require-dev' , 'conflict' , 'provide' , 'replace' , 'suggest' , 'config' , 'autoload' , 'autoload-dev' , 'scripts' , 'scripts-descriptions' , 'support' ) as $prop ) {
2020-11-22 13:26:07 +00:00
if ( isset ( $config [ $prop ]) && $config [ $prop ] === array ()) {
$config [ $prop ] = new \stdClass ;
}
}
foreach ( array ( 'psr-0' , 'psr-4' ) as $prop ) {
if ( isset ( $config [ 'autoload' ][ $prop ]) && $config [ 'autoload' ][ $prop ] === array ()) {
$config [ 'autoload' ][ $prop ] = new \stdClass ;
}
if ( isset ( $config [ 'autoload-dev' ][ $prop ]) && $config [ 'autoload-dev' ][ $prop ] === array ()) {
$config [ 'autoload-dev' ][ $prop ] = new \stdClass ;
}
}
foreach ( array ( 'platform' , 'http-basic' , 'bearer' , 'gitlab-token' , 'gitlab-oauth' , 'github-oauth' , 'preferred-install' ) as $prop ) {
if ( isset ( $config [ 'config' ][ $prop ]) && $config [ 'config' ][ $prop ] === array ()) {
$config [ 'config' ][ $prop ] = new \stdClass ;
2020-02-14 08:33:53 +00:00
}
}
2012-10-18 14:39:47 +00:00
$this -> file -> write ( $config );
}
2012-10-21 16:01:53 +00:00
2020-11-22 13:26:07 +00:00
try {
$this -> file -> validateSchema ( JsonFile :: LAX_SCHEMA );
} catch ( JsonValidationException $e ) {
// restore contents to the original state
file_put_contents ( $this -> file -> getPath (), $contents );
throw new \RuntimeException ( 'Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json).' , 0 , $e );
}
2012-10-21 16:01:53 +00:00
if ( $newFile ) {
2016-01-25 22:37:54 +00:00
Silencer :: call ( 'chmod' , $this -> file -> getPath (), 0600 );
2012-10-21 16:01:53 +00:00
}
2012-10-18 14:39:47 +00:00
}
2014-02-18 09:38:13 +00:00
/**
* Prepend a reference to an element to the beginning of an array .
*
2014-06-10 14:02:44 +00:00
* @ param array $array
* @ param mixed $value
2019-02-19 14:35:48 +00:00
* @ return int
2014-02-18 09:38:13 +00:00
*/
2014-05-16 01:48:30 +00:00
private function arrayUnshiftRef ( & $array , & $value )
2014-02-18 09:38:13 +00:00
{
$return = array_unshift ( $array , '' );
2014-10-17 17:46:01 +00:00
$array [ 0 ] = & $value ;
2014-06-10 14:02:44 +00:00
2014-02-18 09:38:13 +00:00
return $return ;
}
2012-10-18 14:39:47 +00:00
}