| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- <?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
- {
- /**
- * @var bool
- */
- private $useNativeFunction;
- public function __construct()
- {
- $this->useNativeFunction = function_exists('parse_ini_string');
- }
- /**
- * 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)
- {
- $ini = $this->getContentOfIniFile($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)
- {
- // On PHP 5.3.3 an empty line return is needed at the end
- // See http://3v4l.org/jD1Lh
- $ini .= "\n";
- if ($this->useNativeFunction) {
- $array = $this->readWithNativeFunction($ini);
- } else {
- $array = $this->readWithAlternativeImplementation($ini);
- }
- return $array;
- }
- /**
- * @param string $ini
- * @throws IniReadingException
- * @return array
- */
- private function readWithNativeFunction($ini)
- {
- $array = @parse_ini_string($ini, true);
- if ($array === false) {
- $e = error_get_last();
- throw new IniReadingException('Syntax error in INI configuration: ' . $e['message']);
- }
- // We cannot use INI_SCANNER_RAW by default because it is buggy under PHP 5.3.14 and 5.4.4
- // http://3v4l.org/m24cT
- $rawValues = @parse_ini_string($ini, true, INI_SCANNER_RAW);
- $array = $this->decode($array, $rawValues);
- return $array;
- }
- private function getContentOfIniFile($filename)
- {
- if (!file_exists($filename) || !is_readable($filename)) {
- throw new IniReadingException(sprintf("The file %s doesn't exist or is not readable", $filename));
- }
- $content = $this->getFileContent($filename);
- if ($content === false) {
- throw new IniReadingException(sprintf('Impossible to read the file %s', $filename));
- }
- return $content;
- }
- /**
- * Reads ini comments for each key.
- *
- * The array returned is multidimensional, indexed by section names:
- *
- * ```
- * array(
- * 'Section 1' => array(
- * 'key1' => 'comment 1',
- * 'key2' => 'comment 2',
- * ),
- * 'Section 2' => array(
- * 'key3' => 'comment 3',
- * )
- * );
- * ```
- *
- * @param string $filename The path to a file.
- * @throws IniReadingException
- * @return array
- */
- public function readComments($filename)
- {
- $ini = $this->getContentOfIniFile($filename);
- $ini = $this->splitIniContentIntoLines($ini);
- $descriptions = array();
- $section = '';
- $lastComment = '';
- foreach ($ini as $line) {
- $line = trim($line);
- if (strpos($line, '[') === 0) {
- $tmp = explode(']', $line);
- $section = trim(substr($tmp[0], 1));
- $descriptions[$section] = array();
- $lastComment = '';
- continue;
- }
- if (!preg_match('/^[a-zA-Z0-9[]/', $line)) {
- if (strpos($line, ';') === 0) {
- $line = trim(substr($line, 1));
- }
- // comment
- $lastComment .= $line . "\n";
- continue;
- }
- list($key, $value) = explode('=', $line, 2);
- $key = trim($key);
- if (strpos($key, '[]') === strlen($key) - 2) {
- $key = substr($key, 0, -2);
- }
- if (empty($descriptions[$section][$key])) {
- $descriptions[$section][$key] = $lastComment;
- }
- $lastComment = '';
- }
- return $descriptions;
- }
- private function splitIniContentIntoLines($ini)
- {
- if (is_string($ini)) {
- $ini = explode("\n", str_replace("\r", "\n", $ini));
- }
- return $ini;
- }
- /**
- * 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
- * @return array
- */
- private function readWithAlternativeImplementation($ini)
- {
- $ini = $this->splitIniContentIntoLines($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);
- // Special keywords
- if ($value === 'true' || $value === 'yes' || $value === 'on') {
- $value = true;
- } elseif ($value === 'false' || $value === 'no' || $value === 'off') {
- $value = false;
- } elseif ($value === '' || $value === 'null') {
- $value = null;
- }
- if (is_string($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])) {
- $result[$sections[$j]] = $values[$j];
- } else {
- $result[$sections[$j]] = array();
- }
- }
- $finalResult = $result + $globals;
- return $this->decode($finalResult, $finalResult);
- }
- /**
- * @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;
- }
- /**
- * We have to decode values manually because parse_ini_file() has a poor implementation.
- *
- * @param mixed $value The array decoded by `parse_ini_file`
- * @param mixed $rawValue The same array but with raw strings, so that we can re-decode manually
- * and override the poor job of `parse_ini_file`
- * @return mixed
- */
- private function decode($value, $rawValue)
- {
- if (is_array($value)) {
- foreach ($value as $i => &$subValue) {
- $subValue = $this->decode($subValue, $rawValue[$i]);
- }
- return $value;
- }
- if (! is_string($value)) {
- return $value;
- }
- $value = $this->decodeBoolean($value, $rawValue);
- $value = $this->decodeNull($value, $rawValue);
- if (is_numeric($value) && $this->noLossWhenCastToInt($value)) {
- return $value + 0;
- }
- return $value;
- }
- private function decodeBoolean($value, $rawValue)
- {
- if ($value === '1' && ($rawValue === 'true' || $rawValue === 'yes' || $rawValue === 'on')) {
- return true;
- }
- if ($value === '' && ($rawValue === 'false' || $rawValue === 'no' || $rawValue === 'off')) {
- return false;
- }
- return $value;
- }
- private function decodeNull($value, $rawValue)
- {
- if ($value === '' && $rawValue === 'null') {
- return null;
- }
- return $value;
- }
- private function noLossWhenCastToInt($value)
- {
- return (string) ($value + 0) === $value;
- }
- }
|