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