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