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