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