mirror of
				https://github.com/Icinga/icingaweb2.git
				synced 2025-11-04 05:05:01 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			206 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
 | 
						|
 | 
						|
namespace Icinga\File\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 Icinga\Application\Config;
 | 
						|
 | 
						|
/**
 | 
						|
 * A INI file adapter that respects the file structure and the comments of already existing ini files
 | 
						|
 */
 | 
						|
class IniWriter
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * Stores the options
 | 
						|
     *
 | 
						|
     * @var array
 | 
						|
     */
 | 
						|
    protected $options;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The configuration object to write
 | 
						|
     *
 | 
						|
     * @var Config
 | 
						|
     */
 | 
						|
    protected $config;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The mode to set on new files
 | 
						|
     *
 | 
						|
     * @var int
 | 
						|
     */
 | 
						|
    protected $fileMode;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The path to write to
 | 
						|
     *
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    protected $filename;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Create a new INI writer
 | 
						|
     *
 | 
						|
     * @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(Config $config, $filename, $filemode = 0660, $options = array())
 | 
						|
    {
 | 
						|
        $this->config = $config;
 | 
						|
        $this->filename = $filename;
 | 
						|
        $this->fileMode = $filemode;
 | 
						|
        $this->options = $options;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Render the Zend_Config into a config filestring
 | 
						|
     *
 | 
						|
     * @return  string
 | 
						|
     */
 | 
						|
    public function render()
 | 
						|
    {
 | 
						|
        if (file_exists($this->filename)) {
 | 
						|
            $oldconfig = Config::fromIni($this->filename);
 | 
						|
            $content = trim(file_get_contents($this->filename));
 | 
						|
        } else {
 | 
						|
            $oldconfig = Config::fromArray(array());
 | 
						|
            $content = '';
 | 
						|
        }
 | 
						|
        $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 bool $exclusiveLock
 | 
						|
     *
 | 
						|
     * @throws Zend_Config_Exception
 | 
						|
     */
 | 
						|
    public function write($filename = null, $exclusiveLock = false)
 | 
						|
    {
 | 
						|
        $filePath = isset($filename) ? $filename : $this->filename;
 | 
						|
        $setMode = false === file_exists($filePath);
 | 
						|
 | 
						|
        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) {
 | 
						|
            // 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));
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 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(Config $newconfig, Document $oldDoc)
 | 
						|
    {
 | 
						|
        $doc = new Document();
 | 
						|
        $dangling = $oldDoc->getCommentsDangling();
 | 
						|
        if (isset($dangling)) {
 | 
						|
            $doc->setCommentsDangling($dangling);
 | 
						|
        }
 | 
						|
        foreach ($newconfig->toArray() as $section => $directives) {
 | 
						|
            $doc->addSection($oldDoc->getSection($section));
 | 
						|
        }
 | 
						|
        return $doc;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Search for created and updated properties and use the editor to create or update these entries
 | 
						|
     *
 | 
						|
     * @param Config     $newconfig  The config representing the state after the change
 | 
						|
     * @param Document   $doc
 | 
						|
     *
 | 
						|
     * @throws ProgrammingError
 | 
						|
     */
 | 
						|
    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 {
 | 
						|
                $domSection = $doc->getSection($section);
 | 
						|
            }
 | 
						|
            foreach ($directives as $key => $value) {
 | 
						|
                if ($value === null) {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
 | 
						|
                if ($value instanceof ConfigObject) {
 | 
						|
                    throw new ProgrammingError('Cannot diff recursive configs');
 | 
						|
                }
 | 
						|
                if ($domSection->hasDirective($key)) {
 | 
						|
                    $domSection->getDirective($key)->setValue($value);
 | 
						|
                } else {
 | 
						|
                    $dir = new Directive($key);
 | 
						|
                    $dir->setValue($value);
 | 
						|
                    $domSection->addDirective($dir);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Search for deleted properties and use the editor to delete these entries
 | 
						|
     *
 | 
						|
     * @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(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;
 | 
						|
            }
 | 
						|
 | 
						|
            if ($newconfig->hasSection($section)) {
 | 
						|
                $newSection = $newconfig->getSection($section);
 | 
						|
                $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);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |