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