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