1<?php 2 3namespace dokuwiki\plugin\config\core; 4 5use dokuwiki\plugin\config\core\Setting\Setting; 6use dokuwiki\Logger; 7 8/** 9 * Writes the settings to the correct local file 10 */ 11class 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