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;
|
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}
|
|
|
|
*/
|
2012-10-18 14:39:47 +00:00
|
|
|
public function addRepository($name, $config)
|
|
|
|
{
|
2012-10-18 16:43:31 +00:00
|
|
|
$this->manipulateJson('addRepository', $name, $config, function (&$config, $repo, $repoConfig) {
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-18 14:39:47 +00:00
|
|
|
$config['repositories'][$repo] = $repoConfig;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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) {
|
2018-04-13 11:10:22 +00:00
|
|
|
if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
|
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)
|
|
|
|
{
|
|
|
|
$authConfig = $this->authConfig;
|
|
|
|
$this->manipulateJson('removeProperty', $name, function (&$config, $key) {
|
2018-04-13 11:10:22 +00:00
|
|
|
if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
|
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]);
|
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()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_readable($this->file->getPath())) {
|
|
|
|
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
|
|
|
|
foreach (array('require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev') as $linkType) {
|
|
|
|
if (isset($config[$linkType]) && $config[$linkType] === array()) {
|
|
|
|
$config[$linkType] = new \stdClass;
|
|
|
|
}
|
|
|
|
}
|
2012-10-18 14:39:47 +00:00
|
|
|
$this->file->write($config);
|
|
|
|
}
|
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
|
|
|
}
|