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