1<?php
2
3namespace dokuwiki\plugin\config\core;
4use dokuwiki\plugin\config\core\Setting\Setting;
5use dokuwiki\Logger;
6
7/**
8 * Writes the settings to the correct local file
9 */
10class Writer {
11    /** @var string header info */
12    protected $header = 'Dokuwiki\'s Main Configuration File - Local Settings';
13
14    /** @var string the file where the config will be saved to */
15    protected $savefile;
16
17    /**
18     * Writer constructor.
19     */
20    public function __construct() {
21        global $config_cascade;
22        $this->savefile = end($config_cascade['main']['local']);
23    }
24
25    /**
26     * Save the given settings
27     *
28     * @param Setting[] $settings
29     * @throws \Exception
30     */
31    public function save($settings) {
32        global $conf;
33        if($this->isLocked()) throw new \Exception('no save');
34
35        // backup current file (remove any existing backup)
36        if(file_exists($this->savefile)) {
37            if(file_exists($this->savefile . '.bak.php')) @unlink($this->savefile . '.bak.php');
38            if(!io_rename($this->savefile, $this->savefile . '.bak.php')) throw new \Exception('no backup');
39        }
40
41        if(!$fh = @fopen($this->savefile, 'wb')) {
42            io_rename($this->savefile . '.bak.php', $this->savefile); // problem opening, restore the backup
43            throw new \Exception('no save');
44        }
45
46        $out = $this->getHeader();
47        foreach($settings as $setting) {
48            if($setting->shouldBeSaved()) {
49                $out .= $setting->out('conf', 'php');
50            }
51        }
52
53        fwrite($fh, $out);
54        fclose($fh);
55        if($conf['fperm']) chmod($this->savefile, $conf['fperm']);
56        $this->opcacheUpdate($this->savefile);
57    }
58
59    /**
60     * Update last modified time stamp of the config file
61     *
62     * Will invalidate all DokuWiki caches
63     *
64     * @throws \Exception when the config isn't writable
65     */
66    public function touch() {
67        if($this->isLocked()) throw new \Exception('no save');
68        @touch($this->savefile);
69        $this->opcacheUpdate($this->savefile);
70    }
71
72    /**
73     * Invalidate the opcache of the given file (if possible)
74     *
75     * @todo this should probably be moved to core
76     * @param string $file
77     */
78    protected function opcacheUpdate($file) {
79        if(!function_exists('opcache_invalidate')) return;
80        set_error_handler(function ($errNo, $errMsg) {
81            Logger::debug('Unable to invalidate opcache: ' . $errMsg); }
82        );
83        opcache_invalidate($file);
84        restore_error_handler();
85    }
86
87    /**
88     * Configuration is considered locked if there is no local settings filename
89     * or the directory its in is not writable or the file exists and is not writable
90     *
91     * @return bool true: locked, false: writable
92     */
93    public function isLocked() {
94        if(!$this->savefile) return true;
95        if(!is_writable(dirname($this->savefile))) return true;
96        if(file_exists($this->savefile) && !is_writable($this->savefile)) return true;
97        return false;
98    }
99
100    /**
101     * Returns the PHP intro header for the config file
102     *
103     * @return string
104     */
105    protected function getHeader() {
106        return join(
107            "\n",
108            array(
109                '<?php',
110                '/*',
111                ' * ' . $this->header,
112                ' * Auto-generated by config plugin',
113                ' * Run for user: ' . (isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : 'Unknown'),
114                ' * Date: ' . date('r'),
115                ' */',
116                '',
117                ''
118            )
119        );
120    }
121}
122