1<?php 2 3// phpcs:disable PSR1.Files.SideEffects 4/** 5 * Core Manager for the Farm functionality 6 * 7 * This class is initialized before any other DokuWiki code runs. Therefore it is 8 * completely selfcontained and does not use any of DokuWiki's utility functions. 9 * 10 * It's registered as a global $FARMCORE variable but you should not interact with 11 * it directly. Instead use the Farmer plugin's helper component. 12 * 13 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 14 * @author Andreas Gohr <gohr@cosmocode.de> 15 */ 16class DokuWikiFarmCore 17{ 18 /** 19 * @var array The default config - changed by loadConfig 20 */ 21 protected $config = [ 22 'base' => [ 23 'farmdir' => '', 24 'farmhost' => '', 25 'basedomain' => '' 26 ], 27 'notfound' => [ 28 'show' => 'farmer', 29 'url' => '' 30 ], 31 'inherit' => [ 32 'main' => 1, 33 'acronyms' => 1, 34 'entities' => 1, 35 'interwiki' => 1, 36 'license' => 1, 37 'mime' => 1, 38 'scheme' => 1, 39 'smileys' => 1, 40 'wordblock' => 1, 41 'users' => 0, 42 'plugins' => 0, 43 'userstyle' => 0, 44 'userscript' => 0, 45 'styleini' => 0 46 ] 47 ]; 48 49 /** @var string|false The current animal, false for farmer */ 50 protected $animal = false; 51 /** @var bool true if an animal was requested but was not found */ 52 protected $notfound = false; 53 /** @var bool true if the current animal was requested by host */ 54 protected $hostbased = false; 55 56 /** 57 * DokuWikiFarmCore constructor. 58 * 59 * This initializes the whole farm by loading the configuration and setting 60 * DOKU_CONF depending on the requested animal 61 */ 62 public function __construct() 63 { 64 $this->loadConfig(); 65 if ($this->config['base']['farmdir'] === '') return; // farm setup not complete 66 $this->config['base']['farmdir'] = rtrim($this->config['base']['farmdir'], '/') . '/'; // trailing slash always 67 define('DOKU_FARMDIR', $this->config['base']['farmdir']); 68 69 // animal? 70 $this->detectAnimal(); 71 72 // setup defines 73 define('DOKU_FARM_ANIMAL', $this->animal); 74 if ($this->animal) { 75 define('DOKU_CONF', DOKU_FARMDIR . $this->animal . '/conf/'); 76 } else { 77 define('DOKU_CONF', DOKU_INC . '/conf/'); 78 } 79 80 $this->setupCascade(); 81 $this->adjustCascade(); 82 } 83 84 /** 85 * @return array the current farm configuration 86 */ 87 public function getConfig() 88 { 89 return $this->config; 90 } 91 92 /** 93 * @return false|string 94 */ 95 public function getAnimal() 96 { 97 return $this->animal; 98 } 99 100 /** 101 * @return boolean 102 */ 103 public function isHostbased() 104 { 105 return $this->hostbased; 106 } 107 108 /** 109 * @return boolean 110 */ 111 public function wasNotfound() 112 { 113 return $this->notfound; 114 } 115 116 /** 117 * @return string 118 */ 119 public function getAnimalDataDir() 120 { 121 return DOKU_FARMDIR . $this->getAnimal() . '/data/'; 122 } 123 124 /** 125 * @return string 126 */ 127 public function getAnimalBaseDir() 128 { 129 if ($this->isHostbased()) return '/'; 130 return getBaseURL() . '!' . $this->getAnimal(); 131 } 132 133 /** 134 * Detect the current animal 135 * 136 * Sets internal members $animal, $notfound and $hostbased 137 * 138 * This borrows form DokuWiki's inc/farm.php but does not support a default conf dir 139 */ 140 protected function detectAnimal() 141 { 142 $farmdir = $this->config['base']['farmdir']; 143 $farmhost = $this->config['base']['farmhost']; 144 145 // check if animal was set via rewrite parameter 146 $animal = ''; 147 if (isset($_GET['animal'])) { 148 $animal = $_GET['animal']; 149 // now unset the parameter to not leak into new queries 150 unset($_GET['animal']); 151 $params = []; 152 parse_str($_SERVER['QUERY_STRING'], $params); 153 if (isset($params['animal'])) unset($params['animal']); 154 $_SERVER['QUERY_STRING'] = http_build_query($params); 155 } 156 // get animal from CLI parameter 157 if ('cli' == PHP_SAPI && isset($_SERVER['animal'])) $animal = $_SERVER['animal']; 158 if ($animal) { 159 // check that $animal is a string and just a directory name and not a path 160 if (!is_string($animal) || strpbrk($animal, '\\/') !== false) { 161 $this->notfound = true; 162 return; 163 }; 164 $animal = strtolower($animal); 165 166 // check if animal exists 167 if (is_dir("$farmdir/$animal/conf")) { 168 $this->animal = $animal; 169 return; 170 } else { 171 $this->notfound = true; 172 return; 173 } 174 } 175 176 // no host - no host based setup. if we're still here then it's the farmer 177 if (!isset($_SERVER['HTTP_HOST'])) return; 178 179 // is this the farmer? 180 if (strtolower($_SERVER['HTTP_HOST']) == $farmhost) { 181 return; 182 } 183 184 // still here? check for host based 185 $this->hostbased = true; 186 $possible = $this->getAnimalNamesForHost($_SERVER['HTTP_HOST']); 187 foreach ($possible as $animal) { 188 if (is_dir("$farmdir/$animal/conf/")) { 189 $this->animal = $animal; 190 return; 191 } 192 } 193 194 // no hit 195 $this->notfound = true; 196 } 197 198 /** 199 * Return a list of possible animal names for the given host 200 * 201 * @param string $host the HTTP_HOST header 202 * @return array 203 */ 204 protected function getAnimalNamesForHost($host) 205 { 206 $animals = []; 207 $parts = explode('.', implode('.', explode(':', rtrim($host, '.')))); 208 for ($j = count($parts); $j > 0; $j--) { 209 // strip from the end 210 $animals[] = implode('.', array_slice($parts, 0, $j)); 211 // strip from the end without host part 212 $animals[] = implode('.', array_slice($parts, 1, $j)); 213 } 214 $animals = array_unique($animals); 215 $animals = array_filter($animals); 216 usort( 217 $animals, 218 // compare by length, then alphabet 219 function ($a, $b) { 220 $ret = strlen($b) - strlen($a); 221 if ($ret != 0) return $ret; 222 return $a <=> $b; 223 } 224 ); 225 return $animals; 226 } 227 228 /** 229 * This sets up the default farming config cascade 230 */ 231 protected function setupCascade() 232 { 233 global $config_cascade; 234 $config_cascade = [ 235 'main' => [ 236 'default' => [DOKU_INC . 'conf/dokuwiki.php'], 237 'local' => [DOKU_CONF . 'local.php'], 238 'protected' => [DOKU_CONF . 'local.protected.php'] 239 ], 240 'acronyms' => [ 241 'default' => [DOKU_INC . 'conf/acronyms.conf'], 242 'local' => [DOKU_CONF . 'acronyms.local.conf'] 243 ], 244 'entities' => [ 245 'default' => [DOKU_INC . 'conf/entities.conf'], 246 'local' => [DOKU_CONF . 'entities.local.conf'] 247 ], 248 'interwiki' => [ 249 'default' => [DOKU_INC . 'conf/interwiki.conf'], 250 'local' => [DOKU_CONF . 'interwiki.local.conf'] 251 ], 252 'license' => [ 253 'default' => [DOKU_INC . 'conf/license.php'], 254 'local' => [DOKU_CONF . 'license.local.php'] 255 ], 256 'manifest' => [ 257 'default' => [DOKU_INC . 'conf/manifest.json'], 258 'local' => [DOKU_CONF . 'manifest.local.json'] 259 ], 260 'mediameta' => [ 261 'default' => [DOKU_INC . 'conf/mediameta.php'], 262 'local' => [DOKU_CONF . 'mediameta.local.php'] 263 ], 264 'mime' => [ 265 'default' => [DOKU_INC . 'conf/mime.conf'], 266 'local' => [DOKU_CONF . 'mime.local.conf'] 267 ], 268 'scheme' => [ 269 'default' => [DOKU_INC . 'conf/scheme.conf'], 270 'local' => [DOKU_CONF . 'scheme.local.conf'] 271 ], 272 'smileys' => [ 273 'default' => [DOKU_INC . 'conf/smileys.conf'], 274 'local' => [DOKU_CONF . 'smileys.local.conf'] 275 ], 276 'wordblock' => [ 277 'default' => [DOKU_INC . 'conf/wordblock.conf'], 278 'local' => [DOKU_CONF . 'wordblock.local.conf'] 279 ], 280 'acl' => [ 281 'default' => DOKU_CONF . 'acl.auth.php' 282 ], 283 'plainauth.users' => [ 284 'default' => DOKU_CONF . 'users.auth.php' 285 ], 286 'plugins' => [ 287 'default' => [DOKU_INC . 'conf/plugins.php'], 288 'local' => [DOKU_CONF . 'plugins.local.php'], 289 'protected' => [ 290 DOKU_INC . 'conf/plugins.required.php', 291 DOKU_CONF . 'plugins.protected.php' 292 ] 293 ], 294 'userstyle' => [ 295 'screen' => [ 296 DOKU_CONF . 'userstyle.css', 297 DOKU_CONF . 'userstyle.less' 298 ], 299 'print' => [ 300 DOKU_CONF . 'userprint.css', 301 DOKU_CONF . 'userprint.less' 302 ], 303 'feed' => [ 304 DOKU_CONF . 'userfeed.css', 305 DOKU_CONF . 'userfeed.less' 306 ], 307 'all' => [ 308 DOKU_CONF . 'userall.css', 309 DOKU_CONF . 'userall.less' 310 ] 311 ], 312 'userscript' => [ 313 'default' => [DOKU_CONF . 'userscript.js'] 314 ], 315 'styleini' => [ 316 'default' => [DOKU_INC . 'lib/tpl/%TEMPLATE%/' . 'style.ini'], 317 'local' => [DOKU_CONF . 'tpl/%TEMPLATE%/' . 'style.ini'] 318 ] 319 ]; 320 } 321 322 /** 323 * This adds additional files to the config cascade based on the inheritence settings 324 * 325 * These are only added for animals, not the farmer 326 */ 327 protected function adjustCascade() 328 { 329 // nothing to do when on the farmer: 330 if (!$this->animal) return; 331 332 global $config_cascade; 333 foreach ($this->config['inherit'] as $key => $val) { 334 if (!$val) continue; 335 336 // prepare what is to append or prepend 337 $append = []; 338 $prepend = []; 339 if ($key == 'main') { 340 $prepend = [ 341 'protected' => [DOKU_INC . 'conf/local.protected.php'] 342 ]; 343 $append = [ 344 'default' => [DOKU_INC . 'conf/local.php'], 345 'protected' => [DOKU_INC . 'lib/plugins/farmer/includes/config.php'] 346 ]; 347 } elseif ($key == 'license') { 348 $append = [ 349 'default' => [DOKU_INC . 'conf/' . $key . '.local.php'] 350 ]; 351 } elseif ($key == 'userscript') { 352 $prepend = [ 353 'default' => [DOKU_INC . 'conf/userscript.js'] 354 ]; 355 } elseif ($key == 'userstyle') { 356 $prepend = [ 357 'screen' => [ 358 DOKU_INC . 'conf/userstyle.css', 359 DOKU_INC . 'conf/userstyle.less' 360 ], 361 'print' => [ 362 DOKU_INC . 'conf/userprint.css', 363 DOKU_INC . 'conf/userprint.less' 364 ], 365 'feed' => [ 366 DOKU_INC . 'conf/userfeed.css', 367 DOKU_INC . 'conf/userfeed.less' 368 ], 369 'all' => [ 370 DOKU_INC . 'conf/userall.css', 371 DOKU_INC . 'conf/userall.less' 372 ] 373 ]; 374 } elseif ($key == 'styleini') { 375 $append = [ 376 'local' => [DOKU_INC . 'conf/tpl/%TEMPLATE%/style.ini'] 377 ]; 378 } elseif ($key == 'users') { 379 $config_cascade['plainauth.users']['protected'] = DOKU_INC . 'conf/users.auth.php'; 380 } elseif ($key == 'plugins') { 381 $prepend = [ 382 'protected' => [DOKU_INC . 'conf/local.protected.php'] 383 ]; 384 $append = [ 385 'default' => [DOKU_INC . 'conf/plugins.local.php'] 386 ]; 387 } else { 388 $append = [ 389 'default' => [DOKU_INC . 'conf/' . $key . '.local.conf'] 390 ]; 391 } 392 393 // add to cascade 394 foreach ($prepend as $section => $data) { 395 $config_cascade[$key][$section] = array_merge($data, $config_cascade[$key][$section]); 396 } 397 foreach ($append as $section => $data) { 398 $config_cascade[$key][$section] = array_merge($config_cascade[$key][$section], $data); 399 } 400 } 401 402 // add plugin overrides 403 $config_cascade['plugins']['protected'][] = DOKU_INC . 'lib/plugins/farmer/includes/plugins.php'; 404 } 405 406 /** 407 * Loads the farm config 408 */ 409 protected function loadConfig() 410 { 411 $ini = DOKU_INC . 'conf/farm.ini'; 412 if (file_exists($ini)) { 413 $config = parse_ini_file($ini, true); 414 foreach (array_keys($this->config) as $section) { 415 if (isset($config[$section])) { 416 $this->config[$section] = array_merge( 417 $this->config[$section], 418 $config[$section] 419 ); 420 } 421 } 422 } 423 424 // farmdir setup can be done via environment 425 if ($this->config['base']['farmdir'] === '' && isset($_ENV['DOKU_FARMDIR'])) { 426 $this->config['base']['farmdir'] = $_ENV['DOKU_FARMDIR']; 427 } 428 429 $this->config['base']['farmdir'] = trim($this->config['base']['farmdir']); 430 $this->config['base']['farmhost'] = strtolower(trim($this->config['base']['farmhost'])); 431 } 432} 433 434// initialize it globally 435if (!defined('DOKU_UNITTEST')) { 436 global $FARMCORE; 437 $FARMCORE = new DokuWikiFarmCore(); 438} 439