2022-02-23 15:58:18 +00:00
< ? php declare ( strict_types = 1 );
2012-10-18 14:39:47 +00:00
/*
* 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-12-07 10:03:51 +00:00
use Composer\Pcre\Preg ;
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
*/
2022-02-22 15:47:09 +00:00
public function __construct ( JsonFile $file , bool $authConfig = false )
2012-10-18 14:39:47 +00:00
{
$this -> file = $file ;
2014-05-27 11:50:47 +00:00
$this -> authConfig = $authConfig ;
}
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2014-05-27 11:50:47 +00:00
*/
2022-02-22 21:10:52 +00:00
public function getName () : string
2014-05-27 11:50:47 +00:00
{
return $this -> file -> getPath ();
2012-10-18 14:39:47 +00:00
}
2012-11-14 17:03:11 +00:00
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2012-11-14 17:03:11 +00:00
*/
2022-02-22 21:10:52 +00:00
public function addRepository ( string $name , $config , bool $append = true ) : void
2012-10-18 14:39:47 +00:00
{
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'addRepository' , function ( & $config , $repo , $repoConfig ) use ( $append ) : void {
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' ];
}
2022-02-16 12:24:57 +00:00
}, $name , $config , $append );
2012-10-18 14:39:47 +00:00
}
2012-11-14 17:03:11 +00:00
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2012-11-14 17:03:11 +00:00
*/
2022-02-22 21:10:52 +00:00
public function removeRepository ( string $name ) : void
2012-10-18 14:39:47 +00:00
{
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'removeRepository' , function ( & $config , $repo ) : void {
2012-10-18 14:39:47 +00:00
unset ( $config [ 'repositories' ][ $repo ]);
2022-02-16 12:24:57 +00:00
}, $name );
2012-10-18 14:39:47 +00:00
}
2012-11-14 17:03:11 +00:00
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2012-11-14 17:03:11 +00:00
*/
2022-02-22 21:10:52 +00:00
public function addConfigSetting ( string $name , $value ) : void
2012-10-18 14:39:47 +00:00
{
2016-02-29 14:17:24 +00:00
$authConfig = $this -> authConfig ;
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'addConfigSetting' , function ( & $config , $key , $val ) use ( $authConfig ) : void {
2021-12-07 10:03:51 +00:00
if ( Preg :: isMatch ( '{^(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 ;
}
2022-02-16 12:24:57 +00:00
}, $name , $value );
2012-10-18 14:39:47 +00:00
}
2012-11-14 17:03:11 +00:00
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2012-11-14 17:03:11 +00:00
*/
2022-02-22 21:10:52 +00:00
public function removeConfigSetting ( string $name ) : void
2012-10-18 14:39:47 +00:00
{
2016-02-29 14:17:24 +00:00
$authConfig = $this -> authConfig ;
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'removeConfigSetting' , function ( & $config , $key ) use ( $authConfig ) : void {
2021-12-07 10:03:51 +00:00
if ( Preg :: isMatch ( '{^(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 ]);
}
2022-02-16 12:24:57 +00:00
}, $name );
2012-10-18 14:39:47 +00:00
}
2016-04-27 09:48:21 +00:00
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2016-04-27 09:48:21 +00:00
*/
2022-02-22 21:10:52 +00:00
public function addProperty ( string $name , $value ) : void
2016-04-27 09:48:21 +00:00
{
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'addProperty' , function ( & $config , $key , $val ) : void {
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 ;
}
2022-02-16 12:24:57 +00:00
}, $name , $value );
2016-04-27 09:48:21 +00:00
}
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2016-04-27 09:48:21 +00:00
*/
2022-02-22 21:10:52 +00:00
public function removeProperty ( string $name ) : void
2016-04-27 09:48:21 +00:00
{
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'removeProperty' , function ( & $config , $key ) : void {
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 ]);
}
2022-02-16 12:24:57 +00:00
}, $name );
2016-04-27 09:48:21 +00:00
}
2012-11-14 17:03:11 +00:00
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2012-11-14 17:03:11 +00:00
*/
2022-02-22 21:10:52 +00:00
public function addLink ( string $type , string $name , string $value ) : void
2012-11-14 17:03:11 +00:00
{
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'addLink' , function ( & $config , $type , $name , $value ) : void {
2012-11-14 17:03:11 +00:00
$config [ $type ][ $name ] = $value ;
2022-02-16 12:24:57 +00:00
}, $type , $name , $value );
2012-11-14 17:03:11 +00:00
}
/**
2021-10-27 13:47:42 +00:00
* @ inheritDoc
2012-11-14 17:03:11 +00:00
*/
2022-02-22 21:10:52 +00:00
public function removeLink ( string $type , string $name ) : void
2012-11-14 17:03:11 +00:00
{
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'removeSubNode' , function ( & $config , $type , $name ) : void {
2012-11-14 17:03:11 +00:00
unset ( $config [ $type ][ $name ]);
2022-02-16 12:24:57 +00:00
}, $type , $name );
2022-02-21 12:37:49 +00:00
$this -> manipulateJson ( 'removeMainKeyIfEmpty' , function ( & $config , $type ) : void {
2019-01-28 15:46:58 +00:00
if ( 0 === count ( $config [ $type ])) {
unset ( $config [ $type ]);
}
2022-02-16 12:24:57 +00:00
}, $type );
2012-11-14 17:03:11 +00:00
}
2021-10-18 21:51:43 +00:00
/**
* @ param string $method
* @ param callable $fallback
2022-02-16 12:24:57 +00:00
* @ param mixed ... $args
2021-10-18 21:51:43 +00:00
*
* @ return void
*/
2022-02-22 15:47:09 +00:00
private function manipulateJson ( string $method , callable $fallback , ... $args ) : void
2012-10-18 14:39:47 +00:00
{
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 );
2021-12-07 22:00:48 +00:00
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). ' . PHP_EOL . implode ( PHP_EOL , $e -> getErrors ()), 0 , $e );
2020-11-22 13:26:07 +00:00
}
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 .
*
2021-10-18 21:51:43 +00:00
* @ param mixed [] $array
2014-06-10 14:02:44 +00:00
* @ param mixed $value
2019-02-19 14:35:48 +00:00
* @ return int
2014-02-18 09:38:13 +00:00
*/
2022-02-22 15:47:09 +00:00
private function arrayUnshiftRef ( array & $array , & $value ) : int
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
}