<?php

namespace dokuwiki\plugin\config\core;
use dokuwiki\plugin\config\core\Setting\Setting;
use dokuwiki\Logger;

/**
 * Writes the settings to the correct local file
 */
class Writer {
    /** @var string header info */
    protected $header = 'Dokuwiki\'s Main Configuration File - Local Settings';

    /** @var string the file where the config will be saved to */
    protected $savefile;

    /**
     * Writer constructor.
     */
    public function __construct() {
        global $config_cascade;
        $this->savefile = end($config_cascade['main']['local']);
    }

    /**
     * Save the given settings
     *
     * @param Setting[] $settings
     * @throws \Exception
     */
    public function save($settings) {
        global $conf;
        if($this->isLocked()) throw new \Exception('no save');

        // backup current file (remove any existing backup)
        if(file_exists($this->savefile)) {
            if(file_exists($this->savefile . '.bak.php')) @unlink($this->savefile . '.bak.php');
            if(!io_rename($this->savefile, $this->savefile . '.bak.php')) throw new \Exception('no backup');
        }

        if(!$fh = @fopen($this->savefile, 'wb')) {
            io_rename($this->savefile . '.bak.php', $this->savefile); // problem opening, restore the backup
            throw new \Exception('no save');
        }

        $out = '';
        foreach($settings as $setting) {
            if($setting->shouldBeSaved()) {
                $out .= $setting->out('conf', 'php');
            }
        }

        if($out === '') {
            throw new \Exception('empty config');
        }
        $out = $this->getHeader() . $out;

        fwrite($fh, $out);
        fclose($fh);
        if($conf['fperm']) chmod($this->savefile, $conf['fperm']);
        $this->opcacheUpdate($this->savefile);
    }

    /**
     * Update last modified time stamp of the config file
     *
     * Will invalidate all DokuWiki caches
     *
     * @throws \Exception when the config isn't writable
     */
    public function touch() {
        if($this->isLocked()) throw new \Exception('no save');
        @touch($this->savefile);
        $this->opcacheUpdate($this->savefile);
    }

    /**
     * Invalidate the opcache of the given file (if possible)
     *
     * @todo this should probably be moved to core
     * @param string $file
     */
    protected function opcacheUpdate($file) {
        if(!function_exists('opcache_invalidate')) return;
        set_error_handler(function ($errNo, $errMsg) {
            Logger::debug('Unable to invalidate opcache: ' . $errMsg); }
        );
        opcache_invalidate($file);
        restore_error_handler();
    }

    /**
     * Configuration is considered locked if there is no local settings filename
     * or the directory its in is not writable or the file exists and is not writable
     *
     * @return bool true: locked, false: writable
     */
    public function isLocked() {
        if(!$this->savefile) return true;
        if(!is_writable(dirname($this->savefile))) return true;
        if(file_exists($this->savefile) && !is_writable($this->savefile)) return true;
        return false;
    }

    /**
     * Returns the PHP intro header for the config file
     *
     * @return string
     */
    protected function getHeader() {
        return join(
            "\n",
            array(
                '<?php',
                '/*',
                ' * ' . $this->header,
                ' * Auto-generated by config plugin',
                ' * Run for user: ' . (isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : 'Unknown'),
                ' * Date: ' . date('r'),
                ' */',
                '',
                ''
            )
        );
    }
}
