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