Skip to content
Extraits de code Groupes Projets
Valider 88e7546f rédigé par Matthieu Aubry's avatar Matthieu Aubry
Parcourir les fichiers

Merge pull request #9124 from piwik/config_set_command

Adding new command config:set command to set INI config
parents 58abfc8a ea67e45f
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
......@@ -6,14 +6,14 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API'
### New features
* New segment `actionType` lets you segment all actions of a given type, eg. `actionType==events` or `actionType==downloads`. Action types values are: `pageviews`, `contents`, `sitesearches`, `events`, `outlinks`, `downloads`
* The JavaScript Tracker method `PiwikTracker.setDomains()` can now handle paths. This means when setting eg `_paq.push(['setDomains, '*.piwik.org/website1'])` all link that goes to the same domain `piwik.org` but to any other path than `website1/*` will be treated as outlink.
* The JavaScript Tracker method `PiwikTracker.setDomains()` can now handle paths. This means when setting eg `_paq.push(['setDomains, '*.piwik.org/website1'])` all link that goes to the same domain `piwik.org` but to any other path than `website1/*` will be treated as outlink.
### Internal change
* When generating a new plugin skeleton via `generate:plugin` command, plugin name must now contain only letters and numbers.
* JavaScript Tracker tests no longer require `SQLite`. The existing MySQL configuration for tests is used now. In order to run the tests make sure Piwik is installed and `[database_tests]` is configured in `config/config.ini.php`.
* The definitions for search engine and social network detection have been moved from bundled data files to a separate package (see [https://github.com/piwik/searchengine-and-social-list](https://github.com/piwik/searchengine-and-social-list)).
* In [UI screenshot tests](https://developer.piwik.org/guides/tests-ui), a test environment `configOverride` setting should be no longer overwritten. Instead new values should be added to the existing `configOverride` array in PHP or JavaScript. For example instead of `testEnvironment.configOverride = {group: {name: 1}}` use `testEnvironment.overrideConfig('group', 'name', '1')`.
### New APIs
* Add your own SMS/Text provider by creating a new class in the `SMSProvider` directory of your plugin. The class has to extend `Piwik\Plugins\MobileMessaging\SMSProvider` and implement the required methods.
* Segments can now be composed by a union of multiple segments. To do this set an array of segments that shall be used for that segment `$segment->setUnionOfSegments(array('outlinkUrl', 'downloadUrl'))` instead of defining a SQL column.
......@@ -21,6 +21,9 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API'
### Deprecations
* The method `DB::tableExists` was un-used and has been removed.
### New commands
* New command `config:set` lets you set INI config options from the command line. This command can be used for convenience or for automation.
## Piwik 2.15.0
### New commands
......
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreAdminHome\Commands;
use Piwik\Config;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Plugins\CoreAdminHome\Commands\SetConfig\ConfigSettingManipulation;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class SetConfig extends ConsoleCommand
{
protected function configure()
{
$this->setName('config:set');
$this->setDescription('Set one or more config settings in the file config/config.ini.php');
$this->addArgument('assignment', InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
"List of config setting assignments, eg, Section.key=1 or Section.array_key[]=value");
$this->addOption('section', null, InputOption::VALUE_REQUIRED, 'The section the INI config setting belongs to.');
$this->addOption('key', null, InputOption::VALUE_REQUIRED, 'The name of the INI config setting.');
$this->addOption('value', null, InputOption::VALUE_REQUIRED, 'The value of the setting. (Not JSON encoded)');
$this->setHelp("This command can be used to set INI config settings on a Piwik instance.
You can set config values two ways, via --section, --key, --value or by command arguments.
To use --section, --key, --value, simply supply those options. You can only set one setting this way, and you cannot
append to arrays.
To use arguments, supply one or more arguments in the following format: Section.config_setting_name=\"value\"
'Section' is the name of the section, 'config_setting_name' the name of the setting and 'value' is the value.
NOTE: 'value' must be JSON encoded, so Section.config_setting_name=\"value\" would work but
Section.config_setting_name=value would not.
To append to an array setting, supply an argument like this: Section.config_setting_name[]=\"value to append\"
To reset an array setting, supply an argument like this: Section.config_setting_name=[]
Resetting an array will not work if the array has default values in global.ini.php (such as, [log] log_writers).
In this case the values in global.ini.php will be used, since there is no way to explicitly set an
array setting to empty in INI config.
Use the --piwik-domain option to specify which instance to modify.
");
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$section = $input->getOption('section');
$key = $input->getOption('key');
$value = $input->getOption('value');
$manipulations = $this->getAssignments($input);
$isSingleAssignment = !empty($section) && !empty($key) && $value !== false;
if ($isSingleAssignment) {
$manipulations[] = new ConfigSettingManipulation($section, $key, $value);
}
if (empty($manipulations)) {
throw new \InvalidArgumentException("Nothing to assign. Add assignments as arguments or use the "
. "--section, --key and --value options.");
}
$config = Config::getInstance();
foreach ($manipulations as $manipulation) {
$manipulation->manipulate($config);
$output->writeln("<info>Setting [{$manipulation->getSectionName()}] {$manipulation->getName()} = {$manipulation->getValueString()}</info>");
}
$config->forceSave();
$this->writeSuccessMessage($output, array("Done."));
}
/**
* @return ConfigSettingManipulation[]
*/
private function getAssignments(InputInterface $input)
{
$assignments = $input->getArgument('assignment');
$result = array();
foreach ($assignments as $assignment) {
$result[] = ConfigSettingManipulation::make($assignment);
}
return $result;
}
}
\ No newline at end of file
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreAdminHome\Commands\SetConfig;
use Piwik\Config;
/**
* Representation of a INI config manipulation operation. Only supports two types
* of manipulations: appending to a config array and assigning a config value.
*/
class ConfigSettingManipulation
{
/**
* @var string
*/
private $sectionName;
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $value;
/**
* @var bool
*/
private $isArrayAppend;
/**
* @param string $sectionName
* @param string $name
* @param string $value
* @param bool $isArrayAppend
*/
public function __construct($sectionName, $name, $value, $isArrayAppend = false)
{
$this->sectionName = $sectionName;
$this->name = $name;
$this->value = $value;
$this->isArrayAppend = $isArrayAppend;
}
/**
* Performs the INI config manipulation.
*
* @param Config $config
* @throws \Exception if trying to append to a non-array setting value or if trying to set an
* array value to a non-array setting
*/
public function manipulate(Config $config)
{
if ($this->isArrayAppend) {
$this->appendToArraySetting($config);
} else {
$this->setSingleConfigValue($config);
}
}
private function setSingleConfigValue(Config $config)
{
$sectionName = $this->sectionName;
$section = $config->$sectionName;
if (isset($section[$this->name])
&& is_array($section[$this->name])
&& !is_array($this->value)
) {
throw new \Exception("Trying to set non-array value to array setting " . $this->getSettingString() . ".");
}
$section[$this->name] = $this->value;
$config->$sectionName = $section;
}
private function appendToArraySetting(Config $config)
{
$sectionName = $this->sectionName;
$section = $config->$sectionName;
if (isset($section[$this->name])
&& !is_array($section[$this->name])
) {
throw new \Exception("Trying to append to non-array setting value " . $this->getSettingString() . ".");
}
$section[$this->name][] = $this->value;
$config->$sectionName = $section;
}
/**
* Creates a ConfigSettingManipulation instance from a string like:
*
* `SectionName.setting_name=value`
*
* or
*
* `SectionName.setting_name[]=value`
*
* The value must be JSON so `="string"` will work but `=string` will not.
*
* @param string $assignment
* @return self
*/
public static function make($assignment)
{
if (!preg_match('/^([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)(\[\])?=(.*)/', $assignment, $matches)) {
throw new \InvalidArgumentException("Invalid assignment string '$assignment': expected section.name=value or section.name[]=value");
}
$section = $matches[1];
$name = $matches[2];
$isAppend = !empty($matches[3]);
$value = json_decode($matches[4], $isAssoc = true);
if ($value === null) {
throw new \InvalidArgumentException("Invalid assignment string '$assignment': could not parse value as JSON");
}
return new self($section, $name, $value, $isAppend);
}
private function getSettingString()
{
return "[{$this->sectionName}] {$this->name}";
}
/**
* @return string
*/
public function getSectionName()
{
return $this->sectionName;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* @return boolean
*/
public function isArrayAppend()
{
return $this->isArrayAppend;
}
/**
* @return string
*/
public function getValueString()
{
return json_encode($this->value);
}
}
\ No newline at end of file
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreAdminHome\tests\Integration\Commands;
use Interop\Container\ContainerInterface;
use Piwik\Application\Kernel\GlobalSettingsProvider;
use Piwik\Config;
use Piwik\Tests\Framework\TestCase\ConsoleCommandTestCase;
use Piwik\Url;
/**
* @group CoreAdminHome
* @group CoreAdminHome_Integration
*/
class SetConfigTest extends ConsoleCommandTestCase
{
const TEST_CONFIG_PATH = '/tmp/test.config.ini.php';
public static function setUpBeforeClass()
{
self::removeTestConfigFile();
parent::setUpBeforeClass();
}
public function setUp()
{
self::removeTestConfigFile();
parent::setUp();
}
public function test_Command_SucceedsWhenOptionsUsed()
{
$code = $this->applicationTester->run(array(
'command' => 'config:set',
'--section' => 'MySection',
'--key' => 'setting',
'--value' => 'myvalue',
'-vvv' => false,
));
$this->assertEquals(0, $code, $this->getCommandDisplayOutputErrorMessage());
$config = $this->makeNewConfig();
$this->assertEquals(array('setting' => 'myvalue'), $config->MySection);
$this->assertContains('Setting [MySection] setting = "myvalue"', $this->applicationTester->getDisplay());
}
/**
* @dataProvider getInvalidArgumentsForTest
*/
public function test_Command_FailsWhenInvalidArgumentsUsed($invalidArgument)
{
$code = $this->applicationTester->run(array(
'command' => 'config:set',
'assignment' => array($invalidArgument),
'-vvv' => false,
));
$this->assertNotEquals(0, $code, $this->getCommandDisplayOutputErrorMessage());
$this->assertContains('Invalid assignment string', $this->applicationTester->getDisplay());
}
public function getInvalidArgumentsForTest()
{
return array(
array("garbage"),
array("ab&cd.ghi=23"),
array("section.value = 34"),
array("section.value = notjson"),
array("section.array[0]=23"),
);
}
public function test_Command_SucceedsWhenArgumentsUsed()
{
$config = Config::getInstance();
$config->General['trusted_hosts'] = array('www.trustedhost.com');
$config->MySection['other_array_value'] = array('1', '2');
$config->forceSave();
$code = $this->applicationTester->run(array(
'command' => 'config:set',
'assignment' => array(
'General.action_url_category_delimiter="+"',
'General.trusted_hosts[]="www.trustedhost2.com"',
'MySection.array_value=["abc","def"]',
'MySection.object_value={"abc":"def"}',
'MySection.other_array_value=[]',
),
'-vvv' => false,
));
$this->assertEquals(0, $code, $this->getCommandDisplayOutputErrorMessage());
$config = self::makeNewConfig(); // create a new config instance so we read what's in the file
$this->assertEquals('+', $config->General['action_url_category_delimiter']);
$this->assertEquals(array('www.trustedhost.com', 'www.trustedhost2.com'), $config->General['trusted_hosts']);
$this->assertEquals(array('abc', 'def'), $config->MySection['array_value']);
$this->assertEquals(array('def'), $config->MySection['object_value']);
$this->assertArrayNotHasKey('other_array_value', $config->MySection);
$this->assertContains("Done.", $this->applicationTester->getDisplay());
}
/**
* @dataProvider getOptionsForSettingValueToZeroTests
*/
public function test_Command_SucceedsWhenSettingValueToZero($options)
{
$config = Config::getInstance();
$config->Tracker['debug'] = 1;
$config->forceSave();
$code = $this->applicationTester->run($options);
$this->assertEquals(0, $code, $this->getCommandDisplayOutputErrorMessage());
$config = self::makeNewConfig();
$this->assertEquals(0, $config->Tracker['debug']);
$this->assertContains("Done.", $this->applicationTester->getDisplay());
}
public function getOptionsForSettingValueToZeroTests()
{
return array(
array(
array(
'command' => 'config:set',
'--section' => 'Tracker',
'--key' => 'debug',
'--value' => 0,
),
),
array(
array(
'command' => 'config:set',
'assignment' => array(
'Tracker.debug=0',
),
),
),
);
}
private static function getTestConfigFilePath()
{
return PIWIK_INCLUDE_PATH . self::TEST_CONFIG_PATH;
}
public static function provideContainerConfigBeforeClass()
{
return array(
// use a config instance that will save to a test INI file
'Piwik\Config' => function (ContainerInterface $c) {
/** @var GlobalSettingsProvider $actualGlobalSettingsProvider */
$actualGlobalSettingsProvider = $c->get('Piwik\Application\Kernel\GlobalSettingsProvider');
$config = SetConfigTest::makeNewConfig();
// copy over sections required for tests
$config->tests = $actualGlobalSettingsProvider->getSection('tests');
$config->database = $actualGlobalSettingsProvider->getSection('database');
$config->database_tests = $actualGlobalSettingsProvider->getSection('database_tests');
return $config;
},
);
}
private static function makeNewConfig()
{
$settings = new GlobalSettingsProvider(null, SetConfigTest::getTestConfigFilePath());
return new Config($settings);
}
private static function removeTestConfigFile()
{
$configPath = self::getTestConfigFilePath();
if (file_exists($configPath)) {
unlink($configPath);
}
}
}
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreAdminHome\tests\Unit\Commands\SetConfig;
use Piwik\Config;
use Piwik\Plugins\CoreAdminHome\Commands\SetConfig\ConfigSettingManipulation;
// phpunit mocks can't return references, so we need a manual one
class DumbMockConfig extends \Piwik\Config
{
/**
* @var array
*/
public $mockConfigData;
public function __construct()
{
// empty
}
public function &__get($sectionName)
{
if (!isset($this->mockConfigData[$sectionName])) {
$this->mockConfigData[$sectionName] = array();
}
$result =& $this->mockConfigData[$sectionName];
return $result;
}
public function __set($sectionName, $section)
{
$this->mockConfigData[$sectionName] = $section;
}
}
/**
* @group CoreAdminHome
* @group CoreAdminHome_Unit
*/
class ConfigSettingManipulationTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Config
*/
private $mockConfig;
protected function setUp()
{
$this->mockConfig = new DumbMockConfig();
$this->mockConfigData = array();
}
/**
* @dataProvider getTestDataForMake
*/
public function test_make_CreatesCorrectManipulation($assignmentString, $expectedSectionName, $expectedSettingName,
$expectedSettingValue, $expectedIsArrayAppend)
{
$manipulation = ConfigSettingManipulation::make($assignmentString);
$this->assertEquals($expectedSectionName, $manipulation->getSectionName());
$this->assertEquals($expectedSettingName, $manipulation->getName());
$this->assertEquals($expectedSettingValue, $manipulation->getValue());
$this->assertEquals($expectedIsArrayAppend, $manipulation->isArrayAppend());
}
public function getTestDataForMake()
{
return array(
// normal assign
array("General.myconfig=0", "General", "myconfig", 0, false),
// array append
array("General.myconfig444[]=5", "General", "myconfig444", 5, true),
// assign array
array("1General1.2config2=[\"abc\",\"def\"]", "1General1", "2config2", array('abc', 'def'), false),
// assign string
array("MySection.value=\"ghi\"", "MySection", "value", "ghi", false),
// assign boolean
array("MySection.value=false", "MySection", "value", false, false),
array("MySection.value=true", "MySection", "value", true, false),
);
}
/**
* @dataProvider getFailureTestDataForMake
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid assignment string
*/
public function test_make_ThrowsWhenInvalidAssignmentStringSupplied($assignmentString)
{
ConfigSettingManipulation::make($assignmentString);
}
public function getFailureTestDataForMake()
{
return array(
array("General&.value=1"),
array("General.val&*ue=12"),
array("General.value=[notjson]"),
array("General.value=notjson"),
array("General.array[abc]=\"def\""),
);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Trying to append to non-array setting value
*/
public function test_manipulate_ThrowsIfAppendingNonArraySetting()
{
$this->mockConfig->mockConfigData['General']['config'] = "5";
$manipulation = new ConfigSettingManipulation("General", "config", "10", true);
$manipulation->manipulate($this->mockConfig);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Trying to set non-array value to array setting
*/
public function test_manipulate_ThrowsIfAssigningNonArrayValue_ToArraySetting()
{
$this->mockConfig->mockConfigData['General']['config'] = array("5");
$manipulation = new ConfigSettingManipulation("General", "config", "10", false);
$manipulation->manipulate($this->mockConfig);
}
/**
* @dataProvider getTestDataForManipulate
*/
public function test_manipulate_CorrectlyManipulatesConfig($sectionName, $name, $value, $isArrayAppend, $expectedConfig)
{
$manipulation = new ConfigSettingManipulation($sectionName, $name, $value, $isArrayAppend);
$manipulation->manipulate($this->mockConfig);
$this->assertEquals($expectedConfig, $this->mockConfig->mockConfigData);
}
public function getTestDataForManipulate()
{
return array(
// normal assign (string, int, array, bool)
array("Section", "config_setting", "stringvalue", false, array("Section" => array("config_setting" => "stringvalue"))),
array("Section", "config_setting", 25, false, array("Section" => array("config_setting" => 25))),
array("Section", "config_setting", array('a' => 'b'), false, array("Section" => array("config_setting" => array('a' => 'b')))),
array("Section", "config_setting", false, false, array("Section" => array("config_setting" => false))),
// array append
array("Section", "config_setting", "value", true, array("Section" => array("config_setting" => array('value')))),
array("Section", "config_setting", array(1,2), true, array("Section" => array("config_setting" => array(array(1,2))))),
);
}
}
\ No newline at end of file
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter