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