diff --git a/core/Config.php b/core/Config.php index db2e1e5d6a7637fc7c33e7ee9d4617aae50d9634..d6c259881928ad9c1f1a689fefdc3d7fa19c3a65 100644 --- a/core/Config.php +++ b/core/Config.php @@ -10,6 +10,9 @@ namespace Piwik; use Exception; +use Piwik\Ini\IniReader; +use Piwik\Ini\IniReadingException; +use Piwik\Ini\IniWriter; /** * Singleton that provides read & write access to Piwik's INI configuration. @@ -64,13 +67,22 @@ class Config extends Singleton protected $isTest = false; /** - * Constructor + * @var IniReader */ + private $iniReader; + + /** + * @var IniWriter + */ + private $iniWriter; + public function __construct($pathGlobal = null, $pathLocal = null, $pathCommon = null) { $this->pathGlobal = $pathGlobal ?: self::getGlobalConfigPath(); $this->pathCommon = $pathCommon ?: self::getCommonConfigPath(); $this->pathLocal = $pathLocal ?: self::getLocalConfigPath(); + $this->iniReader = new IniReader(); + $this->iniWriter = new IniWriter(); } /** @@ -317,20 +329,33 @@ class Config extends Singleton throw new Exception(Piwik::translate('General_ExceptionConfigurationFileNotFound', array($this->pathGlobal))); } - $this->configGlobal = _parse_ini_file($this->pathGlobal, true); - - if (empty($this->configGlobal) && $reportError) { - throw new Exception(Piwik::translate('General_ExceptionUnreadableFileDisabledMethod', array($this->pathGlobal, "parse_ini_file()"))); + try { + $this->configGlobal = $this->iniReader->readFile($this->pathGlobal); + } catch (IniReadingException $e) { + if ($reportError) { + throw new Exception(Piwik::translate('General_ExceptionUnreadableFileDisabledMethod', array($this->pathGlobal, "parse_ini_file()"))); + } } - $this->configCommon = _parse_ini_file($this->pathCommon, true); + try { + if (file_exists($this->pathCommon)) { + $this->configCommon = $this->iniReader->readFile($this->pathCommon); + } else { + $this->configCommon = false; + } + } catch (IniReadingException $e) { + $this->configCommon = false; + } // Check config.ini.php last $this->checkLocalConfigFound(); - $this->configLocal = _parse_ini_file($this->pathLocal, true); - if (empty($this->configLocal) && $reportError) { - throw new Exception(Piwik::translate('General_ExceptionUnreadableFileDisabledMethod', array($this->pathLocal, "parse_ini_file()"))); + try { + $this->configLocal = $this->iniReader->readFile($this->pathLocal); + } catch (IniReadingException $e) { + if ($reportError) { + throw new Exception(Piwik::translate('General_ExceptionUnreadableFileDisabledMethod', array($this->pathLocal, "parse_ini_file()"))); + } } } @@ -365,8 +390,10 @@ class Config extends Singleton $value = $this->decodeValues($value); } return $values; + } elseif (is_string($values)) { + return html_entity_decode($values, ENT_COMPAT, 'UTF-8'); } - return html_entity_decode($values, ENT_COMPAT, 'UTF-8'); + return $values; } /** @@ -381,11 +408,9 @@ class Config extends Singleton foreach ($values as &$value) { $value = $this->encodeValues($value); } - } else { - if (is_float($values)) { - $values = Common::forceDotAsSeparatorForDecimalPoint($values); - } - + } elseif (is_float($values)) { + $values = Common::forceDotAsSeparatorForDecimalPoint($values); + } elseif (is_string($values)) { $values = htmlentities($values, ENT_COMPAT, 'UTF-8'); $values = str_replace('$', '$', $values); } @@ -556,13 +581,12 @@ class Config extends Singleton { $dirty = false; - $output = "; <?php exit; ?> DO NOT REMOVE THIS LINE\n"; - $output .= "; file automatically generated or modified by Piwik; you can manually override the default values in global.ini.php by redefining them in this file.\n"; - if (!$configCache) { return false; } + $configToWrite = array(); + // If there is a common.config.ini.php, this will ensure config.ini.php does not duplicate its values if (!empty($configCommon)) { $configGlobal = $this->array_merge_recursive_distinct($configGlobal, $configCommon); @@ -605,38 +629,13 @@ class Config extends Singleton $dirty = true; } - // no point in writing empty sections, so skip if the cached section is empty - if (empty($config)) { - continue; - } - - $output .= "[$section]\n"; - - foreach ($config as $name => $value) { - $value = $this->encodeValues($value); - - if (is_numeric($name)) { - $name = $section; - $value = array($value); - } - - if (is_array($value)) { - foreach ($value as $currentValue) { - $output .= $name . "[] = \"$currentValue\"\n"; - } - } else { - if (!is_numeric($value)) { - $value = "\"$value\""; - } - $output .= $name . ' = ' . $value . "\n"; - } - } - - $output .= "\n"; + $configToWrite[$section] = array_map(array($this, 'encodeValues'), $config); } if ($dirty) { - return $output; + $header = "; <?php exit; ?> DO NOT REMOVE THIS LINE\n"; + $header .= "; file automatically generated or modified by Piwik; you can manually override the default values in global.ini.php by redefining them in this file.\n"; + return $this->iniWriter->writeToString($configToWrite, $header); } return false; } diff --git a/core/Ini/IniReader.php b/core/Ini/IniReader.php new file mode 100644 index 0000000000000000000000000000000000000000..e50d79720215fc517bc68b53b82fb217a3d20904 --- /dev/null +++ b/core/Ini/IniReader.php @@ -0,0 +1,262 @@ +<?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\Ini; + +/** + * Reads INI configuration. + */ +class IniReader +{ + /** + * Reads a INI configuration file and returns it as an array. + * + * The array returned is multidimensional, indexed by section names: + * + * ``` + * array( + * 'Section 1' => array( + * 'value1' => 'hello', + * 'value2' => 'world', + * ), + * 'Section 2' => array( + * 'value3' => 'foo', + * ) + * ); + * ``` + * + * @param string $filename The file to read. + * @throws IniReadingException + * @return array + */ + public function readFile($filename) + { + if (!file_exists($filename) || !is_readable($filename)) { + throw new IniReadingException(sprintf("The file %s doesn't exist or is not readable", $filename)); + } + + $ini = $this->getFileContent($filename); + + if ($ini === false) { + throw new IniReadingException(sprintf('Impossible to read the file %s', $filename)); + } + + return $this->readString($ini); + } + + /** + * Reads a INI configuration string and returns it as an array. + * + * The array returned is multidimensional, indexed by section names: + * + * ``` + * array( + * 'Section 1' => array( + * 'value1' => 'hello', + * 'value2' => 'world', + * ), + * 'Section 2' => array( + * 'value3' => 'foo', + * ) + * ); + * ``` + * + * @param string $ini String containing INI configuration. + * @throws IniReadingException + * @return array + */ + public function readString($ini) + { + if (!function_exists('parse_ini_file')) { + return $this->readIni($ini, true); + } + + $array = @parse_ini_string($ini, true, INI_SCANNER_RAW); + + if ($array === false) { + $e = error_get_last(); + throw new IniReadingException('Syntax error in INI configuration: ' . $e['message']); + } + + $array = $this->decodeValues($array); + + return $array; + } + + /** + * Reimplementation in case `parse_ini_file()` is disabled. + * + * @author Andrew Sohn <asohn (at) aircanopy (dot) net> + * @author anthon (dot) pang (at) gmail (dot) com + * + * @param string $ini + * @param bool $processSections + * @return array + */ + private function readIni($ini, $processSections) + { + if (is_string($ini)) { + $ini = explode("\n", str_replace("\r", "\n", $ini)); + } + if (count($ini) == 0) { + return array(); + } + + $sections = array(); + $values = array(); + $result = array(); + $globals = array(); + $i = 0; + foreach ($ini as $line) { + $line = trim($line); + $line = str_replace("\t", " ", $line); + + // Comments + if (!preg_match('/^[a-zA-Z0-9[]/', $line)) { + continue; + } + + // Sections + if ($line{0} == '[') { + $tmp = explode(']', $line); + $sections[] = trim(substr($tmp[0], 1)); + $i++; + continue; + } + + // Key-value pair + list($key, $value) = explode('=', $line, 2); + $key = trim($key); + $value = trim($value); + if (strstr($value, ";")) { + $tmp = explode(';', $value); + if (count($tmp) == 2) { + if ((($value{0} != '"') && ($value{0} != "'")) || + preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) || + preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value) + ) { + $value = $tmp[0]; + } + } else { + if ($value{0} == '"') { + $value = preg_replace('/^"(.*)".*/', '$1', $value); + } elseif ($value{0} == "'") { + $value = preg_replace("/^'(.*)'.*/", '$1', $value); + } else { + $value = $tmp[0]; + } + } + } + + $value = trim($value); + $value = trim($value, "'\""); + + if ($i == 0) { + if (substr($key, -2) == '[]') { + $globals[substr($key, 0, -2)][] = $value; + } else { + $globals[$key] = $value; + } + } else { + if (substr($key, -2) == '[]') { + $values[$i - 1][substr($key, 0, -2)][] = $value; + } else { + $values[$i - 1][$key] = $value; + } + } + } + + for ($j = 0; $j < $i; $j++) { + if (isset($values[$j])) { + if ($processSections === true) { + $result[$sections[$j]] = $values[$j]; + } else { + $result[] = $values[$j]; + } + } else { + if ($processSections === true) { + $result[$sections[$j]] = array(); + } + } + } + + return $result + $globals; + } + + /** + * @param string $filename + * @return bool|string Returns false if failure. + */ + private function getFileContent($filename) + { + if (function_exists('file_get_contents')) { + return file_get_contents($filename); + } elseif (function_exists('file')) { + $ini = file($filename); + if ($ini !== false) { + return implode("\n", $ini); + } + } elseif (function_exists('fopen') && function_exists('fread')) { + $handle = fopen($filename, 'r'); + if (!$handle) { + return false; + } + $ini = fread($handle, filesize($filename)); + fclose($handle); + return $ini; + } + + return false; + } + + private function decodeValues($config) + { + foreach ($config as &$section) { + foreach ($section as $option => $value) { + $section[$option] = $this->decodeValue($value); + } + } + return $config; + } + + /** + * We have to decode values manually because parse_ini_file() has a poor implementation. + * + * @param mixed $value + * @return mixed + */ + private function decodeValue($value) + { + if (is_array($value)) { + foreach ($value as &$subValue) { + $subValue = $this->decodeValue($subValue); + } + return $value; + } + + if (is_numeric($value)) { + return $value + 0; + } + + switch (strtolower($value)) { + case '': + case 'null' : + return null; + case 'true' : + case 'yes' : + case 'on' : + return true; + case 'false': + case 'no': + case 'off': + return false; + } + + return $value; + } +} diff --git a/core/Ini/IniReadingException.php b/core/Ini/IniReadingException.php new file mode 100644 index 0000000000000000000000000000000000000000..227305fd11d9c8f8ca25a5b8f4f5f9a814c11350 --- /dev/null +++ b/core/Ini/IniReadingException.php @@ -0,0 +1,16 @@ +<?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\Ini; + +/** + * Exception when reading a INI configuration. + */ +class IniReadingException extends \Exception +{ +} diff --git a/core/Ini/IniWriter.php b/core/Ini/IniWriter.php new file mode 100644 index 0000000000000000000000000000000000000000..b18af460f47acfccd98a8dcf143f787ba3fe034c --- /dev/null +++ b/core/Ini/IniWriter.php @@ -0,0 +1,120 @@ +<?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\Ini; + +/** + * Writes INI configuration. + */ +class IniWriter +{ + /** + * Writes an array configuration to a INI file. + * + * The array provided must be multidimensional, indexed by section names: + * + * ``` + * array( + * 'Section 1' => array( + * 'value1' => 'hello', + * 'value2' => 'world', + * ), + * 'Section 2' => array( + * 'value3' => 'foo', + * ) + * ); + * ``` + * + * @param string $filename + * @param array $config + * @param string $header Optional header to insert at the top of the file. + * @throws IniWritingException + */ + public function writeToFile($filename, array $config, $header = '') + { + $ini = $this->writeToString($config, $header); + + if (!file_put_contents($filename, $ini)) { + throw new IniWritingException(sprintf('Impossible to write to file %s', $filename)); + } + } + + /** + * Writes an array configuration to a INI string and returns it. + * + * The array provided must be multidimensional, indexed by section names: + * + * ``` + * array( + * 'Section 1' => array( + * 'value1' => 'hello', + * 'value2' => 'world', + * ), + * 'Section 2' => array( + * 'value3' => 'foo', + * ) + * ); + * ``` + * + * @param array $config + * @param string $header Optional header to insert at the top of the file. + * @return string + * @throws IniWritingException + */ + public function writeToString(array $config, $header = '') + { + $ini = $header; + + $sectionNames = array_keys($config); + + foreach ($sectionNames as $sectionName) { + $section = $config[$sectionName]; + + // no point in writing empty sections + if (empty($section)) { + continue; + } + + if (! is_array($section)) { + throw new IniWritingException(sprintf("Section \"%s\" doesn't contain an array of values", $sectionName)); + } + + $ini .= "[$sectionName]\n"; + + foreach ($section as $option => $value) { + if (is_numeric($option)) { + $option = $sectionName; + $value = array($value); + } + + if (is_array($value)) { + foreach ($value as $currentValue) { + $ini .= $option . '[] = ' . $this->encodeValue($currentValue) . "\n"; + } + } else { + $ini .= $option . ' = ' . $this->encodeValue($value) . "\n"; + } + } + + $ini .= "\n"; + } + + return $ini; + } + + private function encodeValue($value) + { + if (is_bool($value)) { + return (int) $value; + } + if (is_string($value)) { + return "\"$value\""; + } + return $value; + } +} diff --git a/core/Ini/IniWritingException.php b/core/Ini/IniWritingException.php new file mode 100644 index 0000000000000000000000000000000000000000..10982fa64d2d1cfaa6b1c8ed0d13261fc988582e --- /dev/null +++ b/core/Ini/IniWritingException.php @@ -0,0 +1,16 @@ +<?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\Ini; + +/** + * Exception when writing a INI configuration. + */ +class IniWritingException extends \Exception +{ +} diff --git a/tests/PHPUnit/Framework/Fixture.php b/tests/PHPUnit/Framework/Fixture.php index 751c4ce1abda7eb3f00edb114d4051d5ec7b6721..d22f3590a52b252d6c94e1c7e025f58824162944 100644 --- a/tests/PHPUnit/Framework/Fixture.php +++ b/tests/PHPUnit/Framework/Fixture.php @@ -18,6 +18,7 @@ use Piwik\Date; use Piwik\Db; use Piwik\DbHelper; use Piwik\EventDispatcher; +use Piwik\Ini\IniReader; use Piwik\Log; use Piwik\Option; use Piwik\Piwik; @@ -826,7 +827,8 @@ class Fixture extends \PHPUnit_Framework_Assert $this->log("Dropping database '$dbName'..."); - $config = _parse_ini_file(PIWIK_INCLUDE_PATH . '/config/config.ini.php', true); + $iniReader = new IniReader(); + $config = $iniReader->readFile(PIWIK_INCLUDE_PATH . '/config/config.ini.php'); $originalDbName = $config['database']['dbname']; if ($dbName == $originalDbName && $dbName != 'piwik_tests' diff --git a/tests/PHPUnit/Integration/ReleaseCheckListTest.php b/tests/PHPUnit/Integration/ReleaseCheckListTest.php index 95aaccb43f667273a2a9c5794e0afdd560f7adf1..189a560c8547ae711b5b8477448a11ba34de9277 100644 --- a/tests/PHPUnit/Integration/ReleaseCheckListTest.php +++ b/tests/PHPUnit/Integration/ReleaseCheckListTest.php @@ -11,6 +11,7 @@ namespace Piwik\Tests\Integration; use Exception; use Piwik\Config; use Piwik\Filesystem; +use Piwik\Ini\IniReader; use Piwik\Plugin\Manager; use Piwik\Tracker; use RecursiveDirectoryIterator; @@ -26,7 +27,8 @@ class ReleaseCheckListTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->globalConfig = _parse_ini_file(PIWIK_PATH_TEST_TO_ROOT . '/config/global.ini.php', true); + $iniReader = new IniReader(); + $this->globalConfig = $iniReader->readFile(PIWIK_PATH_TEST_TO_ROOT . '/config/global.ini.php'); parent::setUp(); } diff --git a/tests/PHPUnit/Unit/ConfigTest.php b/tests/PHPUnit/Unit/ConfigTest.php index 3543871b1f7ff792a65bc73c986aad3ae8e95a9a..2fe32fec68cbb64a3354de73978b6aa78ad91575 100644 --- a/tests/PHPUnit/Unit/ConfigTest.php +++ b/tests/PHPUnit/Unit/ConfigTest.php @@ -238,135 +238,135 @@ class ConfigTest extends PHPUnit_Framework_TestCase // CACHE // --> EXPECTED <-- array('global only, not cached', array( - array(), // local - array('General' => array('debug' => '1')), // global - array(), // common + array(), // local + array('General' => array('debug' => 1)), // global + array(), // common array(), false, )), array('global only, cached get', array( - array(), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('General' => array('debug' => '1')), + array(), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('General' => array('debug' => 1)), false, )), array('global only, cached set', array( - array(), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('General' => array('debug' => '2')), + array(), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('General' => array('debug' => 2)), $header . "[General]\ndebug = 2\n\n", )), array('local copy (same), not cached', array( - array('General' => array('debug' => '1')), // local - array('General' => array('debug' => '1')), // global - array(), // common + array('General' => array('debug' => 1)), // local + array('General' => array('debug' => 1)), // global + array(), // common array(), false, )), array('local copy (same), cached get', array( - array('General' => array('debug' => '1')), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('General' => array('debug' => '1')), + array('General' => array('debug' => 1)), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('General' => array('debug' => 1)), false, )), array('local copy (same), cached set', array( - array('General' => array('debug' => '1')), // local - array('General' => array('debug' => '1')), // global + array('General' => array('debug' => 1)), // local + array('General' => array('debug' => 1)), // global array(), // common - array('General' => array('debug' => '2')), + array('General' => array('debug' => 2)), $header . "[General]\ndebug = 2\n\n", )), array('local copy (different), not cached', array( - array('General' => array('debug' => '2')), // local - array('General' => array('debug' => '1')), // global - array(), // common + array('General' => array('debug' => 2)), // local + array('General' => array('debug' => 1)), // global + array(), // common array(), false, )), array('local copy (different), cached get', array( - array('General' => array('debug' => '2')), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('General' => array('debug' => '2')), + array('General' => array('debug' => 2)), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('General' => array('debug' => 2)), false, )), array('local copy (different), cached set', array( - array('General' => array('debug' => '2')), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('General' => array('debug' => '3')), + array('General' => array('debug' => 2)), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('General' => array('debug' => 3)), $header . "[General]\ndebug = 3\n\n", )), array('local copy, not cached, new section', array( - array('Tracker' => array('anonymize' => '1')), // local - array('General' => array('debug' => '1')), // global - array(), // common + array('Tracker' => array('anonymize' => 1)), // local + array('General' => array('debug' => 1)), // global + array(), // common array(), false, )), array('local copy, cached get, new section', array( - array('Tracker' => array('anonymize' => '1')), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('Tracker' => array('anonymize' => '1')), + array('Tracker' => array('anonymize' => 1)), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('Tracker' => array('anonymize' => 1)), false, )), array('local copy, cached set local, new section', array( - array('Tracker' => array('anonymize' => '1')), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('Tracker' => array('anonymize' => '2')), + array('Tracker' => array('anonymize' => 1)), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('Tracker' => array('anonymize' => 2)), $header . "[Tracker]\nanonymize = 2\n\n", )), array('local copy, cached set global, new section', array( - array('Tracker' => array('anonymize' => '1')), // local - array('General' => array('debug' => '1')), // global - array(), // common - array('General' => array('debug' => '2')), + array('Tracker' => array('anonymize' => 1)), // local + array('General' => array('debug' => 1)), // global + array(), // common + array('General' => array('debug' => 2)), $header . "[General]\ndebug = 2\n\n[Tracker]\nanonymize = 1\n\n", )), array('sort, common sections', array( - array('Tracker' => array('anonymize' => '1'), // local - 'General' => array('debug' => '1')), - array('General' => array('debug' => '0'), // global - 'Tracker' => array('anonymize' => '0')), - array(), // common - array('Tracker' => array('anonymize' => '2')), + array('Tracker' => array('anonymize' => 1), // local + 'General' => array('debug' => 1)), + array('General' => array('debug' => 0), // global + 'Tracker' => array('anonymize' => 0)), + array(), // common + array('Tracker' => array('anonymize' => 2)), $header . "[General]\ndebug = 1\n\n[Tracker]\nanonymize = 2\n\n", )), array('sort, common sections before new section', array( - array('Tracker' => array('anonymize' => '1'), // local - 'General' => array('debug' => '1')), - array('General' => array('debug' => '0'), // global - 'Tracker' => array('anonymize' => '0')), - array(), // common + array('Tracker' => array('anonymize' => 1), // local + 'General' => array('debug' => 1)), + array('General' => array('debug' => 0), // global + 'Tracker' => array('anonymize' => 0)), + array(), // common array('Segment' => array('dimension' => 'foo')), $header . "[General]\ndebug = 1\n\n[Tracker]\nanonymize = 1\n\n[Segment]\ndimension = \"foo\"\n\n", )), array('change back to default', array( - array('Tracker' => array('anonymize' => '1')), // local - array('Tracker' => array('anonymize' => '0'), // global - 'General' => array('debug' => '1')), + array('Tracker' => array('anonymize' => 1)), // local + array('Tracker' => array('anonymize' => 0), // global + 'General' => array('debug' => 1)), array(), // common - array('Tracker' => array('anonymize' => '0')), + array('Tracker' => array('anonymize' => 0)), $header )), diff --git a/tests/PHPUnit/Unit/Ini/IniReaderTest.php b/tests/PHPUnit/Unit/Ini/IniReaderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..190853377c4064fd51caf87b8ebaedc6b2121449 --- /dev/null +++ b/tests/PHPUnit/Unit/Ini/IniReaderTest.php @@ -0,0 +1,105 @@ +<?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\Tests\Unit\Ini; + +use Piwik\Ini\IniReader; + +class IniReaderTest extends \PHPUnit_Framework_TestCase +{ + public function test_readString() + { + $ini = <<<INI +[Section 1] +foo = "bar" +bool_true_1 = 1 +bool_false_1 = 0 +bool_true_2 = true +bool_false_2 = false +bool_true_3 = yes +bool_false_3 = no +bool_true_4 = on +bool_false_4 = off +empty = +explicit_null = null +int = 10 +float = 10.3 +array[] = "string" +array[] = 10.3 +array[] = 1 +array[] = 0 +array[] = true +array[] = false + +[Section 2] +foo = "bar" +INI; + $expected = array( + 'Section 1' => array( + 'foo' => 'bar', + 'bool_true_1' => 1, + 'bool_false_1' => 0, + 'bool_true_2' => true, + 'bool_false_2' => false, + 'bool_true_3' => true, + 'bool_false_3' => false, + 'bool_true_4' => true, + 'bool_false_4' => false, + 'empty' => null, + 'explicit_null' => null, + 'int' => 10, + 'float' => 10.3, + 'array' => array( + 'string', + 10.3, + 1, + 0, + true, + false, + ), + ), + 'Section 2' => array( + 'foo' => 'bar', + ), + ); + $reader = new IniReader(); + $this->assertSame($expected, $reader->readString($ini)); + } + + public function test_readString_withEmptyString() + { + $reader = new IniReader(); + $this->assertSame(array(), $reader->readString('')); + } + + /** + * @expectedException \Piwik\Ini\IniReadingException + * @expectedExceptionMessage Syntax error in INI configuration + */ + public function test_readString_shouldThrowException_ifInvalidIni() + { + $reader = new IniReader(); + $reader->readString('[ test = foo'); + } + + public function test_readString_shouldIgnoreComments() + { + $expected = array( + 'Section 1' => array( + 'foo' => 'bar', + ), + ); + $ini = <<<INI +; <?php exit; ?> DO NOT REMOVE THIS LINE +[Section 1] +foo = "bar" +INI; + $reader = new IniReader(); + $this->assertSame($expected, $reader->readString($ini)); + } +} diff --git a/tests/PHPUnit/Unit/Ini/IniWriterTest.php b/tests/PHPUnit/Unit/Ini/IniWriterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3ff22076d960a496be73ecf6ff9a22fe08a0b71b --- /dev/null +++ b/tests/PHPUnit/Unit/Ini/IniWriterTest.php @@ -0,0 +1,90 @@ +<?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\Tests\Unit\Ini; + +use Piwik\Ini\IniWriter; + +class IniWriterTest extends \PHPUnit_Framework_TestCase +{ + public function test_writeToString() + { + $config = array( + 'Section 1' => array( + 'foo' => 'bar', + 'bool_true' => true, + 'bool_false' => false, + 'int' => 10, + 'float' => 10.3, + 'array' => array( + 'string', + 10.3, + true, + false, + ), + ), + 'Section 2' => array( + 'foo' => 'bar', + ), + ); + $expected = <<<INI +[Section 1] +foo = "bar" +bool_true = 1 +bool_false = 0 +int = 10 +float = 10.3 +array[] = "string" +array[] = 10.3 +array[] = 1 +array[] = 0 + +[Section 2] +foo = "bar" + + +INI; + $writer = new IniWriter(); + $this->assertEquals($expected, $writer->writeToString($config)); + } + + public function test_writeToString_withEmptyConfig() + { + $writer = new IniWriter(); + $this->assertEquals('', $writer->writeToString(array())); + } + + /** + * @expectedException \Piwik\Ini\IniWritingException + * @expectedExceptionMessage Section "Section 1" doesn't contain an array of values + */ + public function test_writeToString_shouldThrowException_withInvalidConfig() + { + $writer = new IniWriter(); + $writer->writeToString(array('Section 1' => 123)); + } + + public function test_writeToString_shouldAddHeader() + { + $header = "; <?php exit; ?> DO NOT REMOVE THIS LINE\n"; + $config = array( + 'Section 1' => array( + 'foo' => 'bar', + ), + ); + $expected = <<<INI +; <?php exit; ?> DO NOT REMOVE THIS LINE +[Section 1] +foo = "bar" + + +INI; + $writer = new IniWriter(); + $this->assertEquals($expected, $writer->writeToString($config, $header)); + } +}