Merge branch 'bugfix/creating-dashlet-with-parentheses-9530'
fixes #9530
This commit is contained in:
commit
60675979bc
|
@ -77,7 +77,21 @@ class DashletForm extends Form
|
|||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('Dashlet Title'),
|
||||
'description' => $this->translate('Enter a title for the dashlet.')
|
||||
'description' => $this->translate('Enter a title for the dashlet.'),
|
||||
'validators' => array(
|
||||
array(
|
||||
'Regex',
|
||||
false,
|
||||
array(
|
||||
'pattern' => '/^[^\\[\\]]+$/',
|
||||
'messages' => array(
|
||||
'regexNotMatch' => $this->translate(
|
||||
'The name cannot contain \'[\' or \']\'.'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
|
|
|
@ -359,11 +359,7 @@ class Config implements Countable, Iterator, Selectable
|
|||
*/
|
||||
protected function getIniWriter($filePath = null, $fileMode = null)
|
||||
{
|
||||
return new IniWriter(array(
|
||||
'config' => $this,
|
||||
'filename' => $filePath,
|
||||
'filemode' => $fileMode
|
||||
));
|
||||
return new IniWriter($this, $filePath, $fileMode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -418,7 +414,6 @@ class Config implements Countable, Iterator, Selectable
|
|||
static::resolvePath('modules/' . $modulename . '/' . $configname . '.ini')
|
||||
);
|
||||
}
|
||||
|
||||
return $moduleConfigs[$configname];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File\Ini\Dom;
|
||||
|
||||
class Comment
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $content;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return ';' . $this->content;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File\Ini\Dom;
|
||||
|
||||
class Directive
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $commentsPre;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $commentPost;
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($key)
|
||||
{
|
||||
$this->key = trim(str_replace("\n", ' ', $key));
|
||||
if (strlen($this->key) < 1) {
|
||||
throw new Exception(sprintf('Ini parser error: empty key.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = trim(str_replace("\n", ' ', $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$str = '';
|
||||
if (! empty ($this->commentsPre)) {
|
||||
$comments = array();
|
||||
foreach ($this->commentsPre as $comment) {
|
||||
$comments[] = $comment->render();
|
||||
}
|
||||
$str = implode(PHP_EOL, $comments) . PHP_EOL;
|
||||
}
|
||||
$str .= sprintf('%s = "%s"', $this->key, $this->value);
|
||||
if (isset ($this->commentPost)) {
|
||||
$str .= ' ' . $this->commentPost->render();
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File\Ini\Dom;
|
||||
|
||||
class Document
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sections = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $commentsDangling;
|
||||
|
||||
/**
|
||||
* @param Section $section
|
||||
*/
|
||||
public function addSection(Section $section)
|
||||
{
|
||||
$this->sections[$section->getName()] = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSection($name)
|
||||
{
|
||||
return isset($this->sections[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Section
|
||||
*/
|
||||
public function getSection($name)
|
||||
{
|
||||
return $this->sections[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Section $section
|
||||
*
|
||||
* @return Section
|
||||
*/
|
||||
public function setSection($name, Section $section)
|
||||
{
|
||||
return $this->sections[$name] = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function removeSection($name)
|
||||
{
|
||||
unset ($this->sections[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
foreach ($this->sections as $section) {
|
||||
$sections []= $section->render();
|
||||
}
|
||||
$str = implode(PHP_EOL, $sections);
|
||||
if (! empty($this->commentsDangling)) {
|
||||
foreach ($this->commentsDangling as $comment) {
|
||||
$str .= PHP_EOL . $comment->render();
|
||||
}
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File\Ini\Dom;
|
||||
|
||||
class Section
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $directives = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $commentsPre;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $commentPost;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = trim(str_replace("\n", ' ', $name));
|
||||
if (strlen($this->name) < 1) {
|
||||
throw new Exception(sprintf('Ini parser error: empty section identifier'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Directive $directive
|
||||
*/
|
||||
public function addDirective(Directive $directive)
|
||||
{
|
||||
$this->directives[$directive->getKey()] = $directive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public function removeDirective($key)
|
||||
{
|
||||
unset ($this->directives[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDirective($key)
|
||||
{
|
||||
return isset($this->directives[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key string
|
||||
*
|
||||
* @return Directive
|
||||
*/
|
||||
public function getDirective($key)
|
||||
{
|
||||
return $this->directives[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$dirs = '';
|
||||
$i = 0;
|
||||
foreach ($this->directives as $directive) {
|
||||
$dirs .= (($i++ > 0 && ! empty($directive->commentsPre)) ? PHP_EOL : '') . $directive->render() . PHP_EOL;
|
||||
}
|
||||
$cms = '';
|
||||
if (! empty($this->commentsPre)) {
|
||||
foreach ($this->commentsPre as $comment) {
|
||||
$comments[] = $comment->render();
|
||||
}
|
||||
$cms = implode(PHP_EOL, $comments) . PHP_EOL;
|
||||
}
|
||||
$post = '';
|
||||
if (isset($this->commentPost)) {
|
||||
$post = ' ' . $this->commentPost->render();
|
||||
}
|
||||
return $cms . sprintf('[%s]', $this->name) . $post . PHP_EOL . $dirs;
|
||||
}
|
||||
}
|
|
@ -1,625 +0,0 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File\Ini;
|
||||
|
||||
/**
|
||||
* Edit the sections and keys of an ini in-place
|
||||
*/
|
||||
class IniEditor
|
||||
{
|
||||
/**
|
||||
* The text that is edited
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* The symbol that is used to separate keys
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $nestSeparator = '.';
|
||||
|
||||
/**
|
||||
* The indentation level of the comments
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $commentIndentation;
|
||||
|
||||
/**
|
||||
* The indentation level of the values
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $valueIndentation;
|
||||
|
||||
/**
|
||||
* The number of new lines between sections
|
||||
*
|
||||
* @var number
|
||||
*/
|
||||
private $sectionSeparators;
|
||||
|
||||
/**
|
||||
* Create a new IniEditor
|
||||
*
|
||||
* @param string $content The content of the ini as string
|
||||
* @param array $options Optional formatting options used when changing the ini file
|
||||
* * valueIndentation: The indentation level of the values
|
||||
* * commentIndentation: The indentation level of the comments
|
||||
* * sectionSeparators: The amount of newlines between sections
|
||||
*/
|
||||
public function __construct(
|
||||
$content,
|
||||
array $options = array()
|
||||
) {
|
||||
$this->text = explode(PHP_EOL, $content);
|
||||
$this->valueIndentation = array_key_exists('valueIndentation', $options)
|
||||
? $options['valueIndentation'] : 19;
|
||||
$this->commentIndentation = array_key_exists('commentIndentation', $options)
|
||||
? $options['commentIndentation'] : 43;
|
||||
$this->sectionSeparators = array_key_exists('sectionSeparators', $options)
|
||||
? $options['sectionSeparators'] : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the given key.
|
||||
*
|
||||
* @param array $key The key to set
|
||||
* @param string $value The value to set
|
||||
* @param array $section The section to insert to.
|
||||
*/
|
||||
public function set(array $key, $value, $section = null)
|
||||
{
|
||||
$line = $this->getKeyLine($key, $section);
|
||||
if ($line === -1) {
|
||||
$this->insert($key, $value, $section);
|
||||
} else {
|
||||
$content = $this->formatKeyValuePair($key, $value);
|
||||
$this->updateLine($line, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the value of the given array element
|
||||
*
|
||||
* @param array $key The key of the array value
|
||||
* @param array $section The section of the array.
|
||||
*/
|
||||
public function resetArrayElement(array $key, $section = null)
|
||||
{
|
||||
$line = $this->getArrayElement($key, $section);
|
||||
if ($line !== -1) {
|
||||
$this->deleteLine($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for an array element
|
||||
*
|
||||
* @param array $key The key of the property
|
||||
* @param string $value The value of the property
|
||||
* @param array $section The section to use
|
||||
*/
|
||||
public function setArrayElement(array $key, $value, $section = null)
|
||||
{
|
||||
$line = $this->getArrayElement($key, $section);
|
||||
if ($line !== -1) {
|
||||
if (isset($section)) {
|
||||
$this->updateLine($line, $this->formatKeyValuePair($key, $value));
|
||||
} else {
|
||||
/*
|
||||
* Move into new section to avoid ambiguous configurations
|
||||
*/
|
||||
$section = $key[0];
|
||||
unset($key[0]);
|
||||
$this->deleteLine($line);
|
||||
$this->setSection($section);
|
||||
$this->insert($key, $value, $section);
|
||||
}
|
||||
} else {
|
||||
$this->insert($key, $value, $section);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line of an array element
|
||||
*
|
||||
* @param array $key The key of the property.
|
||||
* @param mixed $section The section to use
|
||||
*
|
||||
* @return int The line of the array element.
|
||||
*/
|
||||
private function getArrayElement(array $key, $section = null)
|
||||
{
|
||||
$line = isset($section) ? $this->getSectionLine($section) + 1 : 0;
|
||||
$index = array_pop($key);
|
||||
$formatted = $this->formatKey($key);
|
||||
for (; $line < count($this->text); $line++) {
|
||||
$l = $this->text[$line];
|
||||
if ($this->isSectionDeclaration($l)) {
|
||||
return -1;
|
||||
}
|
||||
if (preg_match('/^\s*' . $formatted . '\[\]\s*=/', $l) === 1) {
|
||||
return $line;
|
||||
}
|
||||
if ($this->isPropertyDeclaration($l, array_merge($key, array($index)))) {
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* When it exists, set the key back to null
|
||||
*
|
||||
* @param array $key The key to reset
|
||||
* @param array $section The section of the key
|
||||
*/
|
||||
public function reset(array $key, $section = null)
|
||||
{
|
||||
$line = $this->getKeyLine($key, $section);
|
||||
if ($line === -1) {
|
||||
return;
|
||||
}
|
||||
$this->deleteLine($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the section if it does not exist and set the properties
|
||||
*
|
||||
* @param string $section The section name
|
||||
* @param array $extend The section that should be extended by this section
|
||||
*/
|
||||
public function setSection($section, $extend = null)
|
||||
{
|
||||
if (isset($extend)) {
|
||||
$decl = '[' . $section . ' : ' . $extend . ']';
|
||||
} else {
|
||||
$decl = '[' . $section . ']';
|
||||
}
|
||||
$line = $this->getSectionLine($section);
|
||||
if ($line !== -1) {
|
||||
$this->deleteLine($line);
|
||||
$this->insertAtLine($line, $decl);
|
||||
} else {
|
||||
$line = $this->getLastLine();
|
||||
$this->insertAtLine($line, $decl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the section order of the ini file
|
||||
*
|
||||
* @param array $order An array containing the section names in the new order
|
||||
* Example: array(0 => 'FirstSection', 1 => 'SecondSection')
|
||||
*/
|
||||
public function refreshSectionOrder(array $order)
|
||||
{
|
||||
$sections = $this->createSectionMap($this->text);
|
||||
/*
|
||||
* Move section-less properties to the start of the ordered text
|
||||
*/
|
||||
$orderedText = array();
|
||||
foreach ($sections['[section-less]'] as $line) {
|
||||
array_push($orderedText, $line);
|
||||
}
|
||||
/*
|
||||
* Reorder the sections
|
||||
*/
|
||||
$len = count($order);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if (array_key_exists($i, $order)) {
|
||||
/*
|
||||
* Append the lines of the section to the end of the
|
||||
* ordered text
|
||||
*/
|
||||
foreach ($sections[$order[$i]] as $line) {
|
||||
array_push($orderedText, $line);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->text = $orderedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a map of sections to lines of a given ini file
|
||||
*
|
||||
* @param array $text The text split up in lines
|
||||
*
|
||||
* @return array $sectionMap A map containing all sections as arrays of lines. The array of section-less
|
||||
* lines will be available using they key '[section-less]' which is no valid
|
||||
* section declaration because it contains brackets.
|
||||
*/
|
||||
private function createSectionMap($text)
|
||||
{
|
||||
$sections = array('[section-less]' => array());
|
||||
$section = '[section-less]';
|
||||
$len = count($text);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if ($this->isSectionDeclaration($text[$i])) {
|
||||
$newSection = $this->getSectionFromDeclaration($this->text[$i]);
|
||||
$sections[$newSection] = array();
|
||||
|
||||
/*
|
||||
* Remove comments 'glued' to the new section from the old
|
||||
* section array and put them into the new section.
|
||||
*/
|
||||
$j = $i - 1;
|
||||
$comments = array();
|
||||
while ($j >= 0 && $this->isComment($this->text[$j])) {
|
||||
array_push($comments, array_pop($sections[$section]));
|
||||
$j--;
|
||||
}
|
||||
$comments = array_reverse($comments);
|
||||
foreach ($comments as $comment) {
|
||||
array_push($sections[$newSection], $comment);
|
||||
}
|
||||
|
||||
$section = $newSection;
|
||||
}
|
||||
array_push($sections[$section], $this->text[$i]);
|
||||
}
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the section name from a section declaration
|
||||
*
|
||||
* @param String $declaration The section declaration
|
||||
*
|
||||
* @return string The section name
|
||||
*/
|
||||
private function getSectionFromDeclaration($declaration)
|
||||
{
|
||||
$tmp = preg_split('/(\[|\]|:)/', $declaration);
|
||||
return trim($tmp[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a section declaration
|
||||
*
|
||||
* @param string $section The section name
|
||||
*/
|
||||
public function removeSection($section)
|
||||
{
|
||||
$line = $this->getSectionLine($section);
|
||||
if ($line !== -1) {
|
||||
$this->deleteLine($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the key at the end of the corresponding section
|
||||
*
|
||||
* @param array $key The key to insert
|
||||
* @param mixed $value The value to insert
|
||||
* @param array $section The key to insert
|
||||
*/
|
||||
private function insert(array $key, $value, $section = null)
|
||||
{
|
||||
$line = $this->getSectionEnd($section);
|
||||
$content = $this->formatKeyValuePair($key, $value);
|
||||
$this->insertAtLine($line, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edited text
|
||||
*
|
||||
* @return string The edited text
|
||||
*/
|
||||
public function getText()
|
||||
{
|
||||
$this->normalizeSectionSpacing();
|
||||
|
||||
// trim leading and trailing whitespaces from generated file
|
||||
$txt = trim(implode(PHP_EOL, $this->text)) . PHP_EOL;
|
||||
|
||||
// replace linebreaks, unless they preceed a comment or a section
|
||||
return preg_replace("/\n[\n]*([^;\[])/", "\n$1", $txt);
|
||||
}
|
||||
|
||||
/**
|
||||
* normalize section spacing according to the current settings
|
||||
*/
|
||||
private function normalizeSectionSpacing()
|
||||
{
|
||||
$i = count($this->text) - 1;
|
||||
for (; $i > 0; $i--) {
|
||||
$line = $this->text[$i];
|
||||
if ($this->isSectionDeclaration($line) && $i > 0) {
|
||||
$i--;
|
||||
$line = $this->text[$i];
|
||||
// ignore comments that are glued to the section declaration
|
||||
while ($i > 0 && $this->isComment($line)) {
|
||||
$i--;
|
||||
$line = $this->text[$i];
|
||||
}
|
||||
// remove whitespaces between the sections
|
||||
while ($i > 0 && preg_match('/^\s*$/', $line) === 1) {
|
||||
$this->deleteLine($i);
|
||||
$i--;
|
||||
$line = $this->text[$i];
|
||||
}
|
||||
// refresh section separators
|
||||
if ($i !== 0 && $this->sectionSeparators > 0) {
|
||||
$this->insertAtLine($i + 1, str_repeat(PHP_EOL, $this->sectionSeparators - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the text at line $lineNr
|
||||
*
|
||||
* @param $lineNr The line nr the inserted line should have
|
||||
* @param $toInsert The text that will be inserted
|
||||
*/
|
||||
private function insertAtLine($lineNr, $toInsert)
|
||||
{
|
||||
$this->text = IniEditor::insertIntoArray($this->text, $lineNr, $toInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the line $lineNr
|
||||
*
|
||||
* @param int $lineNr The line number of the target line
|
||||
* @param string $content The new line content
|
||||
*/
|
||||
private function updateLine($lineNr, $content)
|
||||
{
|
||||
$comment = $this->getComment($this->text[$lineNr]);
|
||||
$comment = trim($comment);
|
||||
if (strlen($comment) > 0) {
|
||||
$comment = ' ; ' . $comment;
|
||||
$content = str_pad($content, $this->commentIndentation) . $comment;
|
||||
}
|
||||
$this->text[$lineNr] = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment from the given line
|
||||
*
|
||||
* @param $lineContent The content of the line
|
||||
*
|
||||
* @return string The extracted comment
|
||||
*/
|
||||
private function getComment($lineContent)
|
||||
{
|
||||
/*
|
||||
* Remove all content in double quotes that is not behind a semicolon, recognizing
|
||||
* escaped double quotes inside the string
|
||||
*/
|
||||
$cleaned = preg_replace('/^[^;"]*"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"/s', '', $lineContent);
|
||||
|
||||
$matches = explode(';', $cleaned, 2);
|
||||
return array_key_exists(1, $matches) ? $matches[1] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the line $lineNr
|
||||
*
|
||||
* @param $lineNr The lineNr starting at 0
|
||||
*/
|
||||
private function deleteLine($lineNr)
|
||||
{
|
||||
$this->text = $this->removeFromArray($this->text, $lineNr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a key-value pair to an INI file-entry
|
||||
*
|
||||
* @param array $key The key to format
|
||||
* @param string $value The value to format
|
||||
*
|
||||
* @return string The formatted key-value pair
|
||||
*/
|
||||
private function formatKeyValuePair(array $key, $value)
|
||||
{
|
||||
return str_pad($this->formatKey($key), $this->valueIndentation) . ' = ' . $this->formatValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a key to an INI key
|
||||
*
|
||||
* @param array $key the key array to format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatKey(array $key)
|
||||
{
|
||||
foreach ($key as $i => $separator) {
|
||||
$key[$i] = $this->sanitize($separator);
|
||||
}
|
||||
return implode($this->nestSeparator, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first line after the given $section
|
||||
*
|
||||
* @param $section The name of the section
|
||||
*
|
||||
* @return int The line number of the section
|
||||
*/
|
||||
private function getSectionEnd($section = null)
|
||||
{
|
||||
$i = 0;
|
||||
$started = isset($section) ? false : true;
|
||||
foreach ($this->text as $line) {
|
||||
if ($started && $this->isSectionDeclaration($line)) {
|
||||
if ($i === 0) {
|
||||
return $i;
|
||||
}
|
||||
/*
|
||||
* ignore all comments 'glued' to the next section, to allow section
|
||||
* comments in front of sections
|
||||
*/
|
||||
while ($i > 0 && $this->isComment($this->text[$i - 1])) {
|
||||
$i--;
|
||||
}
|
||||
return $i;
|
||||
} elseif ($this->isSectionDeclaration($line, $section)) {
|
||||
$started = true;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
if (!$started) {
|
||||
return -1;
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given line contains only a comment
|
||||
*/
|
||||
private function isComment($line)
|
||||
{
|
||||
return preg_match('/^\s*;/', $line) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the line contains the property declaration for a key
|
||||
*
|
||||
* @param string $lineContent The content of the line
|
||||
* @param array $key The key this declaration is supposed to have
|
||||
*
|
||||
* @return boolean True, when the lineContent is a property declaration
|
||||
*/
|
||||
private function isPropertyDeclaration($lineContent, array $key)
|
||||
{
|
||||
return preg_match(
|
||||
'/^\s*' . preg_quote($this->formatKey($key), '/') . '\s*=\s*/',
|
||||
$lineContent
|
||||
) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given line contains a section declaration
|
||||
*
|
||||
* @param $lineContent The content of the line
|
||||
* @param string $section The optional section name that will be assumed
|
||||
*
|
||||
* @return bool True, when the lineContent is a section declaration
|
||||
*/
|
||||
private function isSectionDeclaration($lineContent, $section = null)
|
||||
{
|
||||
if (isset($section)) {
|
||||
return preg_match('/^\s*\[\s*' . $section . '\s*[\]:]/', $lineContent) === 1;
|
||||
} else {
|
||||
return preg_match('/^\s*\[/', $lineContent) === 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line where the section begins
|
||||
*
|
||||
* @param $section The section
|
||||
*
|
||||
* @return int The line number
|
||||
*/
|
||||
private function getSectionLine($section)
|
||||
{
|
||||
$i = 0;
|
||||
foreach ($this->text as $line) {
|
||||
if ($this->isSectionDeclaration($line, $section)) {
|
||||
return $i;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line number where the given key occurs
|
||||
*
|
||||
* @param array $keys The key and its parents
|
||||
* @param $section The section of the key
|
||||
*
|
||||
* @return int The line number
|
||||
*/
|
||||
private function getKeyLine(array $keys, $section = null)
|
||||
{
|
||||
$key = implode($this->nestSeparator, $keys);
|
||||
$inSection = isset($section) ? false : true;
|
||||
$i = 0;
|
||||
foreach ($this->text as $line) {
|
||||
if ($inSection && $this->isSectionDeclaration($line)) {
|
||||
return -1;
|
||||
}
|
||||
if ($inSection && $this->isPropertyDeclaration($line, $keys)) {
|
||||
return $i;
|
||||
}
|
||||
if (!$inSection && $this->isSectionDeclaration($line, $section)) {
|
||||
$inSection = true;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last line number occurring in the text
|
||||
*
|
||||
* @return The line number of the last line
|
||||
*/
|
||||
private function getLastLine()
|
||||
{
|
||||
return count($this->text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new element into a specific position of an array
|
||||
*
|
||||
* @param $array The array to use
|
||||
* @param $pos The target position
|
||||
* @param $element The element to insert
|
||||
*
|
||||
* @return array The changed array
|
||||
*/
|
||||
private static function insertIntoArray($array, $pos, $element)
|
||||
{
|
||||
array_splice($array, $pos, 0, $element);
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from an array
|
||||
*
|
||||
* @param $array The array to use
|
||||
* @param $pos The position to remove
|
||||
*
|
||||
* @return array The altered array
|
||||
*/
|
||||
private function removeFromArray($array, $pos)
|
||||
{
|
||||
unset($array[$pos]);
|
||||
return array_values($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a value for INI
|
||||
*
|
||||
* @param $value The value of the string
|
||||
*
|
||||
* @return string The formatted value
|
||||
*
|
||||
* @throws Zend_Config_Exception
|
||||
*/
|
||||
private function formatValue($value)
|
||||
{
|
||||
if (is_integer($value) || is_float($value)) {
|
||||
return $value;
|
||||
} elseif (is_bool($value)) {
|
||||
return ($value ? 'true' : 'false');
|
||||
}
|
||||
return '"' . str_replace('"', '\"', $this->sanitize($value)) . '"';
|
||||
}
|
||||
|
||||
private function sanitize($value)
|
||||
{
|
||||
return str_replace("\n", '', $value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File\Ini;
|
||||
|
||||
use Icinga\File\Ini\Dom\Section;
|
||||
use Icinga\File\Ini\Dom\Comment;
|
||||
use Icinga\File\Ini\Dom\Document;
|
||||
use Icinga\File\Ini\Dom\Directive;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
|
||||
class IniParser
|
||||
{
|
||||
const LINE_START = 0;
|
||||
const SECTION = 1;
|
||||
const DIRECTIVE_KEY = 4;
|
||||
const DIRECTIVE_VALUE_START = 5;
|
||||
const DIRECTIVE_VALUE = 6;
|
||||
const DIRECTIVE_VALUE_QUOTED = 7;
|
||||
const COMMENT = 8;
|
||||
const COMMENT_END = 9;
|
||||
const LINE_END = 10;
|
||||
|
||||
private static function throwParseError($message, $line)
|
||||
{
|
||||
throw new ConfigurationError(sprintf('Ini parser error: %s. (l. %d)', $message, $line));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the ini file contained in a string and return a mutable DOM that can be used
|
||||
* to change the content of an INI file.
|
||||
*
|
||||
* @param $str A string containing the whole ini file
|
||||
*
|
||||
* @return Document The mutable DOM object.
|
||||
* @throws ConfigurationError In case the file is not parseable
|
||||
*/
|
||||
public static function parseIni($str)
|
||||
{
|
||||
$doc = new Document();
|
||||
$sec = null;
|
||||
$dir = null;
|
||||
$coms = array();
|
||||
$state = self::LINE_START;
|
||||
$token = '';
|
||||
$line = 0;
|
||||
|
||||
for ($i = 0; $i < strlen($str); $i++) {
|
||||
$s = $str[$i];
|
||||
switch ($state) {
|
||||
case self::LINE_START:
|
||||
if (ctype_space($s)) {
|
||||
continue;
|
||||
}
|
||||
switch ($s) {
|
||||
case '[':
|
||||
$state = self::SECTION;
|
||||
break;
|
||||
case ';':
|
||||
$state = self::COMMENT;
|
||||
break;
|
||||
default:
|
||||
$state = self::DIRECTIVE_KEY;
|
||||
$token = $s;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::SECTION:
|
||||
if ($s === "\n") {
|
||||
self::throwParseError('Unterminated SECTION', $line);
|
||||
} if ($s !== ']') {
|
||||
$token .= $s;
|
||||
} else {
|
||||
$sec = new Section($token);
|
||||
$sec->commentsPre = $coms;
|
||||
$doc->addSection($sec);
|
||||
$dir = null;
|
||||
$coms = array();
|
||||
|
||||
$state = self::LINE_END;
|
||||
$token = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case self::DIRECTIVE_KEY:
|
||||
if ($s !== '=') {
|
||||
$token .= $s;
|
||||
} else {
|
||||
$dir = new Directive($token);
|
||||
$dir->commentsPre = $coms;
|
||||
|
||||
if (isset($sec)) {
|
||||
$sec->addDirective($dir);
|
||||
} else {
|
||||
Logger::warning(sprintf(
|
||||
'Ini parser warning: section-less directive "%s" ignored. (l. %d)',
|
||||
$token,
|
||||
$line
|
||||
));
|
||||
}
|
||||
|
||||
$coms = array();
|
||||
$state = self::DIRECTIVE_VALUE_START;
|
||||
$token = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case self::DIRECTIVE_VALUE_START:
|
||||
if (ctype_space($s)) {
|
||||
continue;
|
||||
} elseif ($s === '"') {
|
||||
$state = self::DIRECTIVE_VALUE_QUOTED;
|
||||
} else {
|
||||
$state = self::DIRECTIVE_VALUE;
|
||||
$token = $s;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::DIRECTIVE_VALUE:
|
||||
if ($s === "\n" || $s === ";") {
|
||||
$dir->setValue($token);
|
||||
$token = '';
|
||||
|
||||
if ($s === "\n") {
|
||||
$state = self::LINE_START;
|
||||
$line ++;
|
||||
} elseif ($s === ';') {
|
||||
$state = self::COMMENT;
|
||||
}
|
||||
} else {
|
||||
$token .= $s;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::DIRECTIVE_VALUE_QUOTED:
|
||||
if ($s === "\n") {
|
||||
self::throwParseError('Unterminated DIRECTIVE_VALUE_QUOTED', $line);
|
||||
} elseif ($s !== '"') {
|
||||
$token .= $s;
|
||||
} else {
|
||||
$dir->setValue($token);
|
||||
$token = '';
|
||||
$state = self::LINE_END;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::COMMENT:
|
||||
case self::COMMENT_END:
|
||||
if ($s !== "\n") {
|
||||
$token .= $s;
|
||||
} else {
|
||||
$com = new Comment();
|
||||
$com->content = $token;
|
||||
$token = '';
|
||||
|
||||
// Comments at the line end belong to the current line's directive or section. Comments
|
||||
// on empty lines belong to the next directive that shows up.
|
||||
if ($state === self::COMMENT_END) {
|
||||
if (isset($dir)) {
|
||||
$dir->commentPost = $com;
|
||||
} else {
|
||||
$sec->commentPost = $com;
|
||||
}
|
||||
} else {
|
||||
$coms[] = $com;
|
||||
}
|
||||
$state = self::LINE_START;
|
||||
$line ++;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::LINE_END:
|
||||
if ($s === "\n") {
|
||||
$state = self::LINE_START;
|
||||
$line ++;
|
||||
} elseif ($s === ';') {
|
||||
$state = self::COMMENT_END;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// process the last token
|
||||
switch ($state) {
|
||||
case self::COMMENT:
|
||||
case self::COMMENT_END:
|
||||
$com = new Comment();
|
||||
$com->content = $token;
|
||||
if ($state === self::COMMENT_END) {
|
||||
if (isset($dir)) {
|
||||
$dir->commentPost = $com;
|
||||
} else {
|
||||
$sec->commentPost = $com;
|
||||
}
|
||||
} else {
|
||||
$coms[] = $com;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::DIRECTIVE_VALUE:
|
||||
$dir->setValue($token);
|
||||
$sec->addDirective($dir);
|
||||
break;
|
||||
|
||||
case self::DIRECTIVE_VALUE_QUOTED:
|
||||
case self::DIRECTIVE_KEY:
|
||||
case self::SECTION:
|
||||
self::throwParseError('File ended in unterminated state ' . $state, $line);
|
||||
}
|
||||
if (! empty($coms)) {
|
||||
$doc->commentsDangling = $coms;
|
||||
}
|
||||
return $doc;
|
||||
}
|
||||
}
|
|
@ -3,16 +3,19 @@
|
|||
|
||||
namespace Icinga\File\Ini;
|
||||
|
||||
use Zend_Config;
|
||||
use Zend_Config_Ini;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\File\Ini\Dom\Directive;
|
||||
use Icinga\File\Ini\Dom\Document;
|
||||
use Icinga\File\Ini\Dom\Section;
|
||||
use Zend_Config_Exception;
|
||||
use Zend_Config_Writer_FileAbstract;
|
||||
use Icinga\Application\Config;
|
||||
|
||||
/**
|
||||
* A INI file adapter that respects the file structure and the comments of already existing ini files
|
||||
*/
|
||||
class IniWriter extends Zend_Config_Writer_FileAbstract
|
||||
class IniWriter
|
||||
{
|
||||
/**
|
||||
* Stores the options
|
||||
|
@ -21,151 +24,137 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
|
|||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The configuration object to write
|
||||
*
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* The mode to set on new files
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $fileMode = 0660;
|
||||
protected $fileMode;
|
||||
|
||||
/**
|
||||
* The path to write to
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* Create a new INI writer
|
||||
*
|
||||
* @param array $options Supports all options of Zend_Config_Writer and additional options:
|
||||
* * filemode: The mode to set on new files
|
||||
* * valueIndentation: The indentation level of the values
|
||||
* * commentIndentation: The indentation level of the comments
|
||||
* * sectionSeparators: The amount of newlines between sections
|
||||
* @param Config $config The configuration to write
|
||||
* @param string $filename The file name to write to
|
||||
* @param int $filemode Octal file persmissions
|
||||
*
|
||||
* @link http://framework.zend.com/apidoc/1.12/files/Config.Writer.html#\Zend_Config_Writer
|
||||
*/
|
||||
public function __construct(array $options = null)
|
||||
public function __construct(Config $config, $filename, $filemode = 0660, $options = array())
|
||||
{
|
||||
if (isset($options['config']) && $options['config'] instanceof Config) {
|
||||
// As this class inherits from Zend_Config_Writer_FileAbstract we must
|
||||
// not pass the config directly as it needs to be of type Zend_Config
|
||||
$options['config'] = new Zend_Config($options['config']->toArray(), true);
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
$this->filename = $filename;
|
||||
$this->fileMode = $filemode;
|
||||
$this->options = $options;
|
||||
parent::__construct($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the Zend_Config into a config file string
|
||||
* Render the Zend_Config into a config filestring
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
if (file_exists($this->_filename)) {
|
||||
$oldconfig = new Zend_Config_Ini($this->_filename);
|
||||
$content = trim(file_get_contents($this->_filename));
|
||||
if (file_exists($this->filename)) {
|
||||
$oldconfig = Config::fromIni($this->filename);
|
||||
$content = trim(file_get_contents($this->filename));
|
||||
} else {
|
||||
$oldconfig = new Zend_Config(array());
|
||||
$oldconfig = Config::fromArray(array());
|
||||
$content = '';
|
||||
}
|
||||
|
||||
$newconfig = $this->_config;
|
||||
$editor = new IniEditor($content, $this->options);
|
||||
$this->diffConfigs($oldconfig, $newconfig, $editor);
|
||||
$this->updateSectionOrder($newconfig, $editor);
|
||||
return $editor->getText();
|
||||
$doc = IniParser::parseIni($content);
|
||||
$this->diffPropertyUpdates($this->config, $doc);
|
||||
$this->diffPropertyDeletions($oldconfig, $this->config, $doc);
|
||||
$doc = $this->updateSectionOrder($this->config, $doc);
|
||||
return $doc->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write configuration to file and set file mode in case it does not exist yet
|
||||
*
|
||||
* @param string $filename
|
||||
* @param Zend_Config $config
|
||||
* @param bool $exclusiveLock
|
||||
*
|
||||
* @throws Zend_Config_Exception
|
||||
*/
|
||||
public function write($filename = null, Zend_Config $config = null, $exclusiveLock = null)
|
||||
public function write($filename = null, $exclusiveLock = false)
|
||||
{
|
||||
$filePath = $filename !== null ? $filename : $this->_filename;
|
||||
$filePath = isset($filename) ? $filename : $this->filename;
|
||||
$setMode = false === file_exists($filePath);
|
||||
|
||||
parent::write($filename, $config, $exclusiveLock);
|
||||
if (file_put_contents($filePath, $this->render(), $exclusiveLock ? LOCK_EX : 0) === false) {
|
||||
throw new Zend_Config_Exception('Could not write to file "' . $filePath . '"');
|
||||
}
|
||||
|
||||
if ($setMode) {
|
||||
$mode = isset($this->options['filemode']) ? $this->options['filemode'] : static::$fileMode;
|
||||
if (is_int($mode) && false === @chmod($filePath, $mode)) {
|
||||
// file was newly created
|
||||
$mode = $this->fileMode;
|
||||
if (is_int($this->fileMode) && false === @chmod($filePath, $this->fileMode)) {
|
||||
throw new Zend_Config_Exception(sprintf('Failed to set file mode "%o" on file "%s"', $mode, $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a property diff and apply the changes to the editor
|
||||
*
|
||||
* @param Zend_Config $oldconfig The config representing the state before the change
|
||||
* @param Zend_Config $newconfig The config representing the state after the change
|
||||
* @param IniEditor $editor The editor that should be used to edit the old config file
|
||||
* @param array $parents The parent keys that should be respected when editing the config
|
||||
*/
|
||||
protected function diffConfigs(
|
||||
Zend_Config $oldconfig,
|
||||
Zend_Config $newconfig,
|
||||
IniEditor $editor,
|
||||
array $parents = array()
|
||||
) {
|
||||
$this->diffPropertyUpdates($oldconfig, $newconfig, $editor, $parents);
|
||||
$this->diffPropertyDeletions($oldconfig, $newconfig, $editor, $parents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the order of the sections in the ini file to match the order of the new config
|
||||
*
|
||||
* @return Document A new document with the changed section order applied
|
||||
*/
|
||||
protected function updateSectionOrder(Zend_Config $newconfig, IniEditor $editor)
|
||||
protected function updateSectionOrder(Config $newconfig, Document $oldDoc)
|
||||
{
|
||||
$order = array();
|
||||
foreach ($newconfig as $key => $value) {
|
||||
if ($value instanceof Zend_Config) {
|
||||
array_push($order, $key);
|
||||
}
|
||||
$doc = new Document();
|
||||
$doc->commentsDangling = $oldDoc->commentsDangling;
|
||||
foreach ($newconfig->toArray() as $section => $directives) {
|
||||
$doc->addSection($oldDoc->getSection($section));
|
||||
}
|
||||
$editor->refreshSectionOrder($order);
|
||||
return $doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for created and updated properties and use the editor to create or update these entries
|
||||
*
|
||||
* @param Zend_Config $oldconfig The config representing the state before the change
|
||||
* @param Zend_Config $newconfig The config representing the state after the change
|
||||
* @param IniEditor $editor The editor that should be used to edit the old config file
|
||||
* @param array $parents The parent keys that should be respected when editing the config
|
||||
* @param Config $newconfig The config representing the state after the change
|
||||
* @param Document $doc
|
||||
*
|
||||
* @throws ProgrammingError
|
||||
*/
|
||||
protected function diffPropertyUpdates(
|
||||
Zend_Config $oldconfig,
|
||||
Zend_Config $newconfig,
|
||||
IniEditor $editor,
|
||||
array $parents = array()
|
||||
) {
|
||||
// The current section. This value is null when processing the section-less root element
|
||||
$section = empty($parents) ? null : $parents[0];
|
||||
// Iterate over all properties in the new configuration file and search for changes
|
||||
foreach ($newconfig as $key => $value) {
|
||||
$oldvalue = $oldconfig->get($key);
|
||||
$nextParents = array_merge($parents, array($key));
|
||||
$keyIdentifier = empty($parents) ? array($key) : array_slice($nextParents, 1, null, true);
|
||||
if ($value instanceof Zend_Config) {
|
||||
// The value is a nested Zend_Config, handle it recursively
|
||||
if ($section === null) {
|
||||
// Update the section declaration
|
||||
$extends = $newconfig->getExtends();
|
||||
$extend = array_key_exists($key, $extends) ? $extends[$key] : null;
|
||||
$editor->setSection($key, $extend);
|
||||
}
|
||||
if ($oldvalue === null) {
|
||||
$oldvalue = new Zend_Config(array());
|
||||
}
|
||||
$this->diffConfigs($oldvalue, $value, $editor, $nextParents);
|
||||
protected function diffPropertyUpdates(Config $newconfig, Document $doc)
|
||||
{
|
||||
foreach ($newconfig->toArray() as $section => $directives) {
|
||||
if (! is_array($directives)) {
|
||||
Logger::warning('Section-less property ' . (string)$directives . ' was ignored.');
|
||||
continue;
|
||||
}
|
||||
if (!$doc->hasSection($section)) {
|
||||
$domSection = new Section($section);
|
||||
$doc->addSection($domSection);
|
||||
} else {
|
||||
// The value is a plain value, use the editor to set it
|
||||
if (is_numeric($key)) {
|
||||
$editor->setArrayElement($keyIdentifier, $value, $section);
|
||||
$domSection = $doc->getSection($section);
|
||||
}
|
||||
foreach ($directives as $key => $value) {
|
||||
if ($value instanceof ConfigObject) {
|
||||
throw new ProgrammingError('Cannot diff recursive configs');
|
||||
}
|
||||
if ($domSection->hasDirective($key)) {
|
||||
$domSection->getDirective($key)->setValue($value);
|
||||
} else {
|
||||
$editor->set($keyIdentifier, $value, $section);
|
||||
$dir = new Directive($key);
|
||||
$dir->setValue($value);
|
||||
$domSection->addDirective($dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,68 +163,35 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
|
|||
/**
|
||||
* Search for deleted properties and use the editor to delete these entries
|
||||
*
|
||||
* @param Zend_Config $oldconfig The config representing the state before the change
|
||||
* @param Zend_Config $newconfig The config representing the state after the change
|
||||
* @param IniEditor $editor The editor that should be used to edit the old config file
|
||||
* @param array $parents The parent keys that should be respected when editing the config
|
||||
* @param Config $oldconfig The config representing the state before the change
|
||||
* @param Config $newconfig The config representing the state after the change
|
||||
* @param Document $doc
|
||||
*
|
||||
* @throws ProgrammingError
|
||||
*/
|
||||
protected function diffPropertyDeletions(
|
||||
Zend_Config $oldconfig,
|
||||
Zend_Config $newconfig,
|
||||
IniEditor $editor,
|
||||
array $parents = array()
|
||||
) {
|
||||
// The current section. This value is null when processing the section-less root element
|
||||
$section = empty($parents) ? null : $parents[0];
|
||||
|
||||
// Iterate over all properties in the old configuration file and search for deleted properties
|
||||
foreach ($oldconfig as $key => $value) {
|
||||
if ($newconfig->get($key) === null) {
|
||||
$nextParents = array_merge($parents, array($key));
|
||||
$keyIdentifier = empty($parents) ? array($key) : array_slice($nextParents, 1, null, true);
|
||||
foreach ($this->getPropertyIdentifiers($value, $keyIdentifier) as $propertyIdentifier) {
|
||||
$editor->reset($propertyIdentifier, $section);
|
||||
protected function diffPropertyDeletions(Config $oldconfig, Config $newconfig, Document $doc)
|
||||
{
|
||||
// Iterate over all properties in the old configuration file and remove those that don't
|
||||
// exist in the new config
|
||||
foreach ($oldconfig->toArray() as $section => $directives) {
|
||||
if (! is_array($directives)) {
|
||||
Logger::warning('Section-less property ' . (string)$directives . ' was ignored.');
|
||||
continue;
|
||||
}
|
||||
$newSection = $newconfig->getSection($section);
|
||||
if (isset($newSection)) {
|
||||
$oldDomSection = $doc->getSection($section);
|
||||
foreach ($directives as $key => $value) {
|
||||
if ($value instanceof ConfigObject) {
|
||||
throw new ProgrammingError('Cannot diff recursive configs');
|
||||
}
|
||||
if (null === $newSection->get($key) && $oldDomSection->hasDirective($key)) {
|
||||
$oldDomSection->removeDirective($key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$doc->removeSection($section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all possible combinations of property identifiers for the given value
|
||||
*
|
||||
* @param mixed $value The value to return all combinations for
|
||||
* @param array $key The root property identifier, if any
|
||||
*
|
||||
* @return array All property combinations that are possible
|
||||
*
|
||||
* @todo Cannot handle array properties yet (e.g. a.b[]='c')
|
||||
*/
|
||||
protected function getPropertyIdentifiers($value, array $key = null)
|
||||
{
|
||||
$combinations = array();
|
||||
$rootProperty = $key !== null ? $key : array();
|
||||
|
||||
if ($value instanceof Zend_Config) {
|
||||
foreach ($value as $subProperty => $subValue) {
|
||||
$combinations = array_merge(
|
||||
$combinations,
|
||||
$this->getPropertyIdentifiers($subValue, array_merge($rootProperty, array($subProperty)))
|
||||
);
|
||||
}
|
||||
} elseif (is_string($value)) {
|
||||
$combinations[] = $rootProperty;
|
||||
}
|
||||
|
||||
return $combinations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename()
|
||||
{
|
||||
return $this->_filename;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
use Icinga\Application\Icinga;
|
||||
use Icinga\Module\Doc\DocController;
|
||||
use Icinga\Module\Doc\Exception\DocException;
|
||||
use Icinga\File\Ini\Parser;
|
||||
|
||||
class Doc_ModuleController extends DocController
|
||||
{
|
||||
|
|
|
@ -31,86 +31,28 @@ class IniWriterTest extends BaseTestCase
|
|||
public function testWhetherPointInSectionIsNotNormalized()
|
||||
{
|
||||
$writer = new IniWriter(
|
||||
array(
|
||||
'config' => Config::fromArray(
|
||||
array(
|
||||
'section' => array(
|
||||
'foo.bar' => 1337
|
||||
),
|
||||
'section.with.multiple.dots' => array(
|
||||
'some more' => array(
|
||||
'nested stuff' => 'With more values'
|
||||
)
|
||||
)
|
||||
)
|
||||
Config::fromArray(
|
||||
array(
|
||||
'section' => array(
|
||||
'foo.bar' => 1337
|
||||
),
|
||||
'filename' => $this->tempFile
|
||||
)
|
||||
'section.with.multiple.dots' => array(
|
||||
'some more.nested stuff' => 'With more values'
|
||||
)
|
||||
)
|
||||
),
|
||||
$this->tempFile
|
||||
);
|
||||
$writer->write();
|
||||
$config = Config::fromIni($this->tempFile)->toArray();
|
||||
$this->assertTrue(array_key_exists('section.with.multiple.dots', $config), 'Section names not normalized');
|
||||
}
|
||||
|
||||
public function testWhetherSimplePropertiesAreInsertedInEmptyFiles()
|
||||
{
|
||||
$this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore');
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$config = Config::fromArray(array('key' => 'value'));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertEquals('value', $newConfig->get('key'), 'IniWriter does not insert in empty files');
|
||||
}
|
||||
|
||||
public function testWhetherSimplePropertiesAreInsertedInExistingFiles()
|
||||
{
|
||||
$this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore');
|
||||
$target = $this->writeConfigToTemporaryFile('key1 = "1"');
|
||||
$config = Config::fromArray(array('key2' => '2'));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertEquals('2', $newConfig->get('key2'), 'IniWriter does not insert in existing files');
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherSimplePropertiesAreInsertedInExistingFiles
|
||||
*/
|
||||
public function testWhetherSimplePropertiesAreUpdated()
|
||||
{
|
||||
$this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore');
|
||||
$target = $this->writeConfigToTemporaryFile('key = "value"');
|
||||
$config = Config::fromArray(array('key' => 'eulav'));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertEquals('eulav', $newConfig->get('key'), 'IniWriter does not update simple properties');
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherSimplePropertiesAreInsertedInExistingFiles
|
||||
*/
|
||||
public function testWhetherSimplePropertiesAreDeleted()
|
||||
{
|
||||
$this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore');
|
||||
$target = $this->writeConfigToTemporaryFile('key = "value"');
|
||||
$config = new Config();
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertNull($newConfig->get('key'), 'IniWriter does not delete simple properties');
|
||||
}
|
||||
|
||||
public function testWhetherNestedPropertiesAreInserted()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$config = Config::fromArray(array('a' => array('b' => 'c')));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer = new IniWriter($config, $target);
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
|
@ -126,450 +68,54 @@ class IniWriterTest extends BaseTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherNestedPropertiesAreInserted
|
||||
*/
|
||||
public function testWhetherNestedPropertiesAreUpdated()
|
||||
{
|
||||
$this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore');
|
||||
$target = $this->writeConfigToTemporaryFile('a.b = "c"');
|
||||
$config = Config::fromArray(array('a' => array('b' => 'cc')));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('a'),
|
||||
'IniWriter does not update nested properties'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'cc',
|
||||
$newConfig->get('a')->get('b'),
|
||||
'IniWriter does not update nested properties'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherNestedPropertiesAreInserted
|
||||
*/
|
||||
public function testWhetherNestedPropertiesAreDeleted()
|
||||
{
|
||||
$this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore');
|
||||
$target = $this->writeConfigToTemporaryFile('a.b = "c"');
|
||||
$config = new Config();
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertNull(
|
||||
$newConfig->get('a'),
|
||||
'IniWriter does not delete nested properties'
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhetherSimpleSectionPropertiesAreInserted()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$config = Config::fromArray(array('section' => array('key' => 'value')));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertInstanceOf(
|
||||
'Icinga\Data\ConfigObject',
|
||||
$newConfig->getSection('section'),
|
||||
'IniWriter does not insert sections'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'value',
|
||||
$newConfig->getSection('section')->get('key'),
|
||||
'IniWriter does not insert simple section properties'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherSimpleSectionPropertiesAreInserted
|
||||
*/
|
||||
public function testWhetherSimpleSectionPropertiesAreUpdated()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[section]
|
||||
key = "value"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(array('section' => array('key' => 'eulav')));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertEquals(
|
||||
'eulav',
|
||||
$newConfig->getSection('section')->get('key'),
|
||||
'IniWriter does not update simple section properties'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherSimpleSectionPropertiesAreInserted
|
||||
*/
|
||||
public function testWhetherSimpleSectionPropertiesAreDeleted()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[section]
|
||||
key = "value"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(array('section' => array()));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertNull(
|
||||
$newConfig->getSection('section')->get('key'),
|
||||
'IniWriter does not delete simple section properties'
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhetherNestedSectionPropertiesAreInserted()
|
||||
{
|
||||
$this->markTestSkipped('Implementation has changed. Config::fromIni cannot handle nested properties anymore');
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$config = Config::fromArray(array('section' => array('a' => array('b' => 'c'))));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('section'),
|
||||
'IniWriter does not insert sections'
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('section')->get('a'),
|
||||
'IniWriter does not insert nested section properties'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'c',
|
||||
$newConfig->get('section')->get('a')->get('b'),
|
||||
'IniWriter does not insert nested section properties'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherNestedSectionPropertiesAreInserted
|
||||
*/
|
||||
public function testWhetherNestedSectionPropertiesAreUpdated()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[section]
|
||||
a.b = "c"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(array('section' => array('a' => array('b' => 'cc'))));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertEquals(
|
||||
'cc',
|
||||
$newConfig->get('section')->get('a')->get('b'),
|
||||
'IniWriter does not update nested section properties'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherNestedSectionPropertiesAreInserted
|
||||
*/
|
||||
public function testWhetherNestedSectionPropertiesAreDeleted()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[section]
|
||||
a.b = "c"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(array('section' => array()));
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertNull(
|
||||
$newConfig->get('section')->get('a'),
|
||||
'IniWriter does not delete nested section properties'
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhetherSimplePropertiesOfExtendingSectionsAreInserted()
|
||||
{
|
||||
$this->markTestSkipped(
|
||||
'Implementation has changed. There is no "Extend" functionality anymore in our Config object'
|
||||
);
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$config = Config::fromArray(
|
||||
array(
|
||||
'foo' => array('key1' => '1'),
|
||||
'bar' => array('key2' => '2')
|
||||
)
|
||||
);
|
||||
$config->setExtend('bar', 'foo');
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('foo'),
|
||||
'IniWriter does not insert extended sections'
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('bar'),
|
||||
'IniWriter does not insert extending sections'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'2',
|
||||
$newConfig->get('bar')->get('key2'),
|
||||
'IniWriter does not insert simple properties into extending sections'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'1',
|
||||
$newConfig->get('foo')->get('key1'),
|
||||
'IniWriter does not properly define extending sections'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherSimplePropertiesOfExtendingSectionsAreInserted
|
||||
*/
|
||||
public function testWhetherSimplePropertiesOfExtendingSectionsAreUpdated()
|
||||
{
|
||||
$this->markTestSkipped(
|
||||
'Implementation has changed. There is no "Extend" functionality anymore in our Config object'
|
||||
);
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[foo]
|
||||
key1 = "1"
|
||||
|
||||
[bar : foo]
|
||||
key2 = "2"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(
|
||||
array(
|
||||
'foo' => array('key1' => '1'),
|
||||
'bar' => array('key2' => '22')
|
||||
)
|
||||
);
|
||||
$config->setExtend('bar', 'foo');
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertEquals(
|
||||
'22',
|
||||
$newConfig->get('bar')->get('key2'),
|
||||
'IniWriter does not update simple properties of extending sections'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherSimplePropertiesOfExtendingSectionsAreInserted
|
||||
*/
|
||||
public function testWhetherSimplePropertiesOfExtendingSectionsAreDeleted()
|
||||
{
|
||||
$this->markTestSkipped(
|
||||
'Implementation has changed. There is no "Extend" functionality anymore in our Config object'
|
||||
);
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[foo]
|
||||
key1 = "1"
|
||||
|
||||
[bar : foo]
|
||||
key2 = "2"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(
|
||||
array(
|
||||
'foo' => array('key1' => '1'),
|
||||
'bar' => array()
|
||||
)
|
||||
);
|
||||
$config->setExtend('bar', 'foo');
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertNull(
|
||||
$newConfig->get('bar')->get('key2'),
|
||||
'IniWriter does not delete simple properties of extending sections'
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhetherNestedPropertiesOfExtendingSectionsAreInserted()
|
||||
{
|
||||
$this->markTestSkipped(
|
||||
'Implementation has changed. There is no "Extend" functionality anymore in our Config object'
|
||||
);
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$config = Config::fromArray(
|
||||
array(
|
||||
'foo' => array('a' => array('b' => 'c')),
|
||||
'bar' => array('d' => array('e' => 'f'))
|
||||
)
|
||||
);
|
||||
$config->setExtend('bar', 'foo');
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('foo'),
|
||||
'IniWriter does not insert extended sections'
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('bar'),
|
||||
'IniWriter does not insert extending sections'
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
get_class($newConfig),
|
||||
$newConfig->get('bar')->get('d'),
|
||||
'IniWriter does not insert nested properties into extending sections'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'f',
|
||||
$newConfig->get('bar')->get('d')->get('e'),
|
||||
'IniWriter does not insert nested properties into extending sections'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'c',
|
||||
$newConfig->get('bar')->get('a')->get('b'),
|
||||
'IniWriter does not properly define extending sections with nested properties'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherNestedPropertiesOfExtendingSectionsAreInserted
|
||||
*/
|
||||
public function testWhetherNestedPropertiesOfExtendingSectionsAreUpdated()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[foo]
|
||||
a.b = "c"
|
||||
|
||||
[bar : foo]
|
||||
d.e = "f"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(
|
||||
array(
|
||||
'foo' => array('a' => array('b' => 'c')),
|
||||
'bar' => array('d' => array('e' => 'ff'))
|
||||
)
|
||||
);
|
||||
$config->setExtend('bar', 'foo');
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertEquals(
|
||||
'ff',
|
||||
$newConfig->get('bar')->get('d')->get('e'),
|
||||
'IniWriter does not update nested properties of extending sections'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherNestedPropertiesOfExtendingSectionsAreInserted
|
||||
*/
|
||||
public function testWhetherNestedPropertiesOfExtendingSectionsAreDeleted()
|
||||
{
|
||||
$this->markTestSkipped(
|
||||
'Implementation has changed. There is no "Extend" functionality anymore in our Config object'
|
||||
);
|
||||
$target = $this->writeConfigToTemporaryFile(<<<'EOD'
|
||||
[foo]
|
||||
a.b = "c"
|
||||
|
||||
[bar : foo]
|
||||
d.e = "f"
|
||||
EOD
|
||||
);
|
||||
$config = Config::fromArray(
|
||||
array(
|
||||
'foo' => array('a' => array('b' => 'c')),
|
||||
'bar' => array()
|
||||
)
|
||||
);
|
||||
$config->setExtend('bar', 'foo');
|
||||
$writer = new IniWriter(array('config' => $config, 'filename' => $target));
|
||||
$writer->write();
|
||||
|
||||
$newConfig = Config::fromIni($target);
|
||||
$this->assertNull(
|
||||
$newConfig->get('bar')->get('d'),
|
||||
'IniWriter does not delete nested properties of extending sections'
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhetherSectionOrderIsUpdated()
|
||||
{
|
||||
$config = <<<'EOD'
|
||||
[one]
|
||||
key1 = "1"
|
||||
key2 = "2"
|
||||
key1 = "1"
|
||||
key2 = "2"
|
||||
|
||||
[two]
|
||||
a.b = "c"
|
||||
d.e = "f"
|
||||
a.b = "c"
|
||||
d.e = "f"
|
||||
|
||||
[three]
|
||||
key = "value"
|
||||
foo.bar = "raboof"
|
||||
key = "value"
|
||||
foo.bar = "raboof"
|
||||
EOD;
|
||||
|
||||
$reverted = <<<'EOD'
|
||||
[three]
|
||||
key = "value"
|
||||
foo.bar = "raboof"
|
||||
key = "value"
|
||||
foo.bar = "raboof"
|
||||
|
||||
[two]
|
||||
a.b = "c"
|
||||
d.e = "f"
|
||||
a.b = "c"
|
||||
d.e = "f"
|
||||
|
||||
[one]
|
||||
key1 = "1"
|
||||
key2 = "2"
|
||||
key1 = "1"
|
||||
key2 = "2"
|
||||
EOD;
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
array(
|
||||
'config' => Config::fromArray(
|
||||
array(
|
||||
'three' => array(
|
||||
'foo' => array(
|
||||
'bar' => 'raboof'
|
||||
),
|
||||
'key' => 'value'
|
||||
),
|
||||
'two' => array(
|
||||
'd' => array(
|
||||
'e' => 'f'
|
||||
),
|
||||
'a' => array(
|
||||
'b' => 'c'
|
||||
)
|
||||
),
|
||||
'one' => array(
|
||||
'key2' => '2',
|
||||
'key1' => '1'
|
||||
)
|
||||
Config::fromArray(
|
||||
array(
|
||||
'three' => array(
|
||||
'foo.bar' => 'raboof',
|
||||
'key' => 'value'
|
||||
),
|
||||
'two' => array(
|
||||
'd.e' => 'f',
|
||||
'a.b' => 'c'
|
||||
),
|
||||
'one' => array(
|
||||
'key2' => '2',
|
||||
'key1' => '1'
|
||||
)
|
||||
),
|
||||
'filename' => $target
|
||||
)
|
||||
)
|
||||
),
|
||||
$target
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
|
@ -598,15 +144,13 @@ EOD;
|
|||
EOD;
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
array(
|
||||
'config' => Config::fromArray(
|
||||
array(
|
||||
'two' => array(),
|
||||
'one' => array()
|
||||
)
|
||||
),
|
||||
'filename' => $target
|
||||
)
|
||||
Config::fromArray(
|
||||
array(
|
||||
'two' => array(),
|
||||
'one' => array()
|
||||
)
|
||||
),
|
||||
$target
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
|
@ -621,14 +165,14 @@ EOD;
|
|||
{
|
||||
$config = <<<'EOD'
|
||||
; some interesting comment
|
||||
key = "value"
|
||||
; another interesting comment
|
||||
[blarg]
|
||||
key = "value"
|
||||
|
||||
; some dangling comment
|
||||
; boring comment
|
||||
EOD;
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
array('config' => Config::fromArray(array('key' => 'value')), 'filename' => $target)
|
||||
);
|
||||
$writer = new IniWriter(Config::fromArray(array('blarg' => array('key' => 'value'))), $target);
|
||||
|
||||
$this->assertEquals(
|
||||
trim($config),
|
||||
|
@ -640,26 +184,24 @@ EOD;
|
|||
public function testWhetherCommentsOnPropertyLinesArePreserved()
|
||||
{
|
||||
$config = <<<'EOD'
|
||||
foo = 1337 ; I know what a " and a ' is
|
||||
bar = 7331 ; I; tend; to; overact; !1!1!!11!111! ;
|
||||
key = "value" ; some comment for a small sized property
|
||||
xxl = "very loooooooooooooooooooooong" ; my value is very lo...
|
||||
[blarg]
|
||||
foo = "1337" ; I know what a " and a ' is
|
||||
bar = "7331" ; I; tend; to; overact; !1!1!!11!111! ;
|
||||
key = "value" ; some comment for a small sized property
|
||||
xxl = "very loooooooooooooooooooooong" ; my value is very lo...
|
||||
EOD;
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
array(
|
||||
'config' => Config::fromArray(
|
||||
array(
|
||||
'foo' => 1337,
|
||||
'bar' => 7331,
|
||||
'key' => 'value',
|
||||
'xxl' => 'very loooooooooooooooooooooong'
|
||||
)
|
||||
),
|
||||
'filename' => $target
|
||||
)
|
||||
Config::fromArray(
|
||||
array('blarg' => array(
|
||||
'foo' => 1337,
|
||||
'bar' => 7331,
|
||||
'key' => 'value',
|
||||
'xxl' => 'very loooooooooooooooooooooong'
|
||||
))
|
||||
),
|
||||
$target
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
trim($config),
|
||||
trim($writer->render()),
|
||||
|
@ -672,12 +214,10 @@ EOD;
|
|||
$config = <<<'EOD'
|
||||
[section]
|
||||
; some interesting comment, in a section
|
||||
key = "value"
|
||||
key = "value"
|
||||
EOD;
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
array('config' => Config::fromArray(array('section' => array('key' => 'value'))), 'filename' => $target)
|
||||
);
|
||||
$writer = new IniWriter(Config::fromArray(array('section' => array('key' => 'value'))), $target);
|
||||
|
||||
$this->assertEquals(
|
||||
trim($config),
|
||||
|
@ -690,26 +230,24 @@ EOD;
|
|||
{
|
||||
$config = <<<'EOD'
|
||||
[section]
|
||||
foo = 1337 ; I know what a " and a ' is
|
||||
bar = 7331 ; I; tend; to; overact; !1!1!!11!111! ;
|
||||
key = "value" ; some comment for a small sized property
|
||||
xxl = "very loooooooooooooooooooooong" ; my value is very lo...
|
||||
foo = "1337" ; I know what a " and a ' is
|
||||
bar = "7331" ; I; tend; to; overact; !1!1!!11!111! ;
|
||||
key = "value" ; some comment for a small sized property
|
||||
xxl = "very loooooooooooooooooooooong" ; my value is very lo...
|
||||
EOD;
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
array(
|
||||
'config' => Config::fromArray(
|
||||
array(
|
||||
'section' => array(
|
||||
'foo' => 1337,
|
||||
'bar' => 7331,
|
||||
'key' => 'value',
|
||||
'xxl' => 'very loooooooooooooooooooooong'
|
||||
)
|
||||
Config::fromArray(
|
||||
array(
|
||||
'section' => array(
|
||||
'foo' => 1337,
|
||||
'bar' => 7331,
|
||||
'key' => 'value',
|
||||
'xxl' => 'very loooooooooooooooooooooong'
|
||||
)
|
||||
),
|
||||
'filename' => $target
|
||||
)
|
||||
)
|
||||
),
|
||||
$target
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
|
@ -723,19 +261,17 @@ EOD;
|
|||
{
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$writer = new IniWriter(
|
||||
array(
|
||||
'config' => Config::fromArray(
|
||||
array(
|
||||
'section' => array(
|
||||
'foo' => 'linebreak
|
||||
Config::fromArray(
|
||||
array(
|
||||
'section' => array(
|
||||
'foo' => 'linebreak
|
||||
in line',
|
||||
'linebreak
|
||||
'linebreak
|
||||
inkey' => 'blarg'
|
||||
)
|
||||
)
|
||||
),
|
||||
'filename' => $target
|
||||
)
|
||||
)
|
||||
),
|
||||
$target
|
||||
);
|
||||
|
||||
$rendered = $writer->render();
|
||||
|
@ -746,6 +282,81 @@ inkey' => 'blarg'
|
|||
);
|
||||
}
|
||||
|
||||
public function testSectionNameEscaping()
|
||||
{
|
||||
$config = <<<'EOD'
|
||||
[section 1]
|
||||
foo = "1337"
|
||||
|
||||
[section (with special chars)]
|
||||
foo = "baz"
|
||||
|
||||
[section/as/arbitrary/path]
|
||||
foo = "nope"
|
||||
|
||||
[section.with.dots.in.it]
|
||||
foo = "bar"
|
||||
EOD;
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
Config::fromArray(
|
||||
array(
|
||||
'section 1' => array('foo' => 1337),
|
||||
'section (with special chars)' => array('foo' => 'baz'),
|
||||
'section/as/arbitrary/path' => array('foo' => 'nope'),
|
||||
'section.with.dots.in.it' => array('foo' => 'bar')
|
||||
)
|
||||
),
|
||||
$target
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
trim($config),
|
||||
trim($writer->render()),
|
||||
'IniWriter does not handle special chars in section names properly.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSectionDeleted()
|
||||
{
|
||||
$config = <<<'EOD'
|
||||
[section 1]
|
||||
guarg = "1"
|
||||
|
||||
[section 2]
|
||||
foo = "1337"
|
||||
foo2 = "baz"
|
||||
foo3 = "nope"
|
||||
foo4 = "bar"
|
||||
|
||||
[section 3]
|
||||
guard = "2"
|
||||
EOD;
|
||||
$deleted = <<<'EOD'
|
||||
[section 1]
|
||||
guarg = "1"
|
||||
|
||||
[section 3]
|
||||
guard = "2"
|
||||
EOD;
|
||||
|
||||
$target = $this->writeConfigToTemporaryFile($config);
|
||||
$writer = new IniWriter(
|
||||
Config::fromArray(array(
|
||||
'section 1' => array('guarg' => 1),
|
||||
'section 3' => array('guard' => 2)
|
||||
)),
|
||||
$target
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
trim($deleted),
|
||||
trim($writer->render()),
|
||||
'IniWriter does not delete sections properly'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write a INI-configuration string to a temporary file and return its path
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue