setName('config:get'); $this->setDescription('Get a config value or section'); $this->addArgument( 'argument', InputArgument::OPTIONAL, "A config setting in the format Section.key or Section.array_key[], e.g. 'Database.username' or 'PluginsInstalled'" ); $this->addOption('section', 's', InputOption::VALUE_REQUIRED, 'The section the INI config setting belongs to.'); $this->addOption('key', 'k', InputOption::VALUE_REQUIRED, 'The name of the INI config setting.'); $this->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'Format the output as ' . json_encode(self::OUTPUT_FORMATS) . '; Default is ' . self::OUTPUT_FORMAT_DEFAULT, self::OUTPUT_FORMAT_DEFAULT); $this->setHelp("This command can be used to get a INI config setting or a whole section of settings on a Piwik instance. You can get config values per the two sections below, where: - [Section] is the name of the section, e.g. database or General. - [config_setting_name] the name of the setting, e.g. username. (1) By settings options --section=[Section] and --key=[config_setting_name]. The option --section is required. Examples: #Return all settings in this section. $ ./console %command.name% --section=database #Return only this setting. $ ./console %command.name% --section=database --key=username OR (2) By using a command argument in the format [Section].[config_setting_name]. Examples: #Return all settings in this section. $ ./console %command.name% 'database' #Return all settings in this array; square brackets are optional. $ ./console %command.name% 'PluginsInstalled.PluginsInstalled' $ ./console %command.name% 'PluginsInstalled.PluginsInstalled[]' #Return only this setting. $ ./console %command.name% 'database.username' NOTES: - Section and key names are case-sensitive; so e.g. --section=Database fails but --section=database works. Look in global.ini.php for the proper case. - If no matching section/setting is found, the string \"" . self::MSG_NOTHING_FOUND . "\" shows. - Else the output will be shown JSON-encoded. You can use something like https://stedolan.github.io/jq to parse it. - If you ask for 'PluginsInstalled.PluginsInstalled[\"some_array_item\"]', it will ignore the array key (\"some_array_item\") and you will get back the whole array of values (e.g. all PluginsInstalled[] values). "); } protected function execute(InputInterface $input, OutputInterface $output) { // Gather options, then discard ones with an empty value so we do not need to check for empty later. $options = array_filter([ 'section' => $input->getOption('section'), 'key' => $input->getOption('key'), ]); // If none specified, set default format. $format = $input->getOption('format'); if (empty($format) || !in_array($format, self::OUTPUT_FORMATS, true)) { $format = self::OUTPUT_FORMAT_DEFAULT; } $argument = trim($input->getArgument('argument') ?? ''); // If there are multiple arguments, just use the last one. $argument = array_slice(explode(' ', $argument), -1)[0]; // Sanity check inputs. switch (true) { case empty($argument) && empty($options): throw new \InvalidArgumentException('You must set either an argument or set options --section and optional --key'); case (!empty($argument) && !empty($options)): throw new \InvalidArgumentException('You cannot set both an argument (' . serialize($argument) . ') and options (' . serialize($argument) . ')'); case empty($argument) && (!isset($options['section']) || empty($options['section'])): throw new \InvalidArgumentException('When using options, the --section value must be set'); case (!empty($argument)): $settingStr = $argument; break; case (!empty($options)): $settingStr = implode('.', $options); break; default: // We should not get here, but just in case. throw new \Exception('Some unexpected error occurred in ' . __FUNCTION__ . ' at line ' . __LINE__); } // Parse the $settingStr into a SystemConfigSetting object. $setting = self::parseSettingStr($settingStr); $result = $this->getConfigValue(Config::getInstance(), $setting); if (empty($result)) { $output->writeln(self::wrapInTag('comment', self::MSG_NOTHING_FOUND)); } else { $output->writeln($this->formatVariableForOutput($setting, $result, $format)); } //Many matomo script output Done when they're done. IMO it's not needed: $output->writeln(self::wrapInTag('info', 'Done.')); } /** * Get a config section or section.value. * * @param Config $config A Matomo Config instance. * @param SystemConfigSetting $setting A setting object describing what to get e.g. from self::make(). * @return Mixed The config section or value. */ private function getConfigValue(Config $config, SystemConfigSetting $setting) { // This should have been caught in the calling function, so assume a bad implementation and throw an error. if (empty($sectionName = $setting->getConfigSectionName())) { throw new \InvalidArgumentException('A section name must be specified'); } if (empty($section = $config->__get($sectionName))) { return null; } // Convert array to object since it is slightly cleaner/easier to work with. $section = (object) $section; // Look for the specific setting. $settingName = $setting->getName(); // Return the whole setting section if requested. // The name=FAKE_SETTING_NAME is a placeholder for when no setting is specified. if (empty($settingName) || $settingName === self::NO_SETTING_NAME_FOUND_PLACEHOLDER) { $sectionContents = $section; return (array) $sectionContents; } switch (true) { case (!isset($section->$settingName)): $settingValue = null; break; case is_array($settingValue = $section->$settingName): break; default: $settingValue = $setting->getValue(); } return $settingValue; } /** * Build a SystemConfigSetting object from a string. * * @param string $settingStr Config setting string to parse. * @return SystemConfigSetting A SystemConfigSetting object. */ public static function parseSettingStr(string $settingStr): SystemConfigSetting { $matches = []; if (!preg_match('/^([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?(\[\])?/', $settingStr, $matches) || empty($matches[1])) { throw new \InvalidArgumentException("Invalid input string='{$settingStr}' =expected section.name or section.name[]"); } return new SystemConfigSetting( // Setting name. SystemConfigSetting throws an error if the setting name is empty, so use placeholder that flags that no setting was specified. $matches[2] ?? self::NO_SETTING_NAME_FOUND_PLACEHOLDER, // Default value. '', // Type. FieldConfig::TYPE_STRING, // Plugin name. 'core', // Section name. $matches[1] ); } /** * Wrap the input string in an open and closing HTML/XML tag. * E.g. wrap_in_tag('info', 'my string') returns 'my string' * * @param string $tagname Tag name to wrap the string in. * @param string $str String to wrap with the tag. * @return string The wrapped string. */ public static function wrapInTag(string $tagname, string $str): string { return "<$tagname>$str"; } /** * Format the config setting to the specified output format. * * @param SystemConfigSetting $setting The found SystemConfigSetting. * @param mixed $var The config setting -- either scalar or an array of settings. * @param string $format The output format: One of self::OUTPUT_FORMAT_DEFAULT. * @return string The formatted output. */ private function formatVariableForOutput(SystemConfigSetting $setting, $var, string $format = self::OUTPUT_FORMAT_DEFAULT): string { switch ($format) { case 'json': return $this->toJson($var); case 'yaml': return $this->toYaml($var); case 'text': return $this->toText($setting, $var); default: throw new \InvalidArgumentException('Unsupported output format'); } } /** * Convert $var to a YAML string. * Throws an error on invalid types (a PHP resource or object). * * @param mixed $var The variable to convert. * @return string The Yaml-formatted string. */ private function toYaml($var): string { // Remove leading dash and spaces Spyc adds so we just output the bare value. return trim(ltrim(Spyc::YAMLDump($var, 2, 0, true), '-')); } /** * Convert $var to a JSON string. * Throws an error on json_encode failure. * * @param mixed $var The variable to convert. * @return string The JSON-formatted string. */ private function toJson($var): string { $result = json_encode($var, JSON_UNESCAPED_SLASHES); if ($result === false) { throw new \Exception('Failed to json_encode'); } return $result; } /** * Convert $var to Symfony-colorized CLI output text. * * @param SystemConfigSetting $setting The found SystemConfigSetting. * @param mixed $var The thing to format: Config scalar values lead to $var being scalar; Config array values lead to $var being an array of scalars; Config sections lead to $var being a mixed array of both scalar and array values. * @return string The formatted result. */ private function toText(SystemConfigSetting $setting, $var): string { // Strip off the NO_SETTING_NAME_FOUND_PLACEHOLDER. $settingName = $setting->getName() === self::NO_SETTING_NAME_FOUND_PLACEHOLDER ? '' : $setting->getName(); $sectionAndSettingName = implode('.', array_filter([$setting->getConfigSectionName(), $settingName])); $output = ''; switch (true) { case is_array($var): $output .= $this->wrapInTag('info', ($settingName ? $sectionAndSettingName : "[{$sectionAndSettingName}]") . PHP_EOL); $output .= $this->wrapInTag('info', '--' . PHP_EOL); foreach ($var as $thisSettingName => &$val) { if (is_array($val)) { foreach ($val as &$arrayVal) { $output .= $this->wrapInTag('info', "{$thisSettingName}[] = " . $this->wrapInTag('comment', $arrayVal)) . PHP_EOL; } } else { $output .= $this->wrapInTag('info', $thisSettingName . ' = ' . $this->wrapInTag('comment', $val)) . PHP_EOL; } } $output .= $this->wrapInTag('info', '--' . PHP_EOL); break; case is_scalar($var): $output .= $this->wrapInTag('info', $sectionAndSettingName . ' = ' . $this->wrapInTag('comment', $var)); break; default: throw \InvalidArgumentException('Cannot output unknown type'); } return $output; } }