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