xref: /plugin/farmer/DokuWikiFarmCore.php (revision 47978fa40c309bf2cd36bbdc80195d529dcd6b78)
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