xref: /plugin/farmer/DokuWikiFarmCore.php (revision de1560159380287a983849ba90e2418164f1744a)
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(
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        // add plugin overrides
312        $config_cascade['plugins']['protected'][] = DOKU_INC . 'lib/plugins/farmer/includes/plugins.php';
313    }
314
315    /**
316     * Loads the farm config
317     */
318    protected function loadConfig() {
319        $ini = DOKU_INC . 'conf/farm.ini';
320        if(!file_exists($ini)) return;
321        $config = parse_ini_file($ini, true);
322        foreach(array_keys($this->config) as $section) {
323            if(isset($config[$section])) {
324                $this->config[$section] = array_merge(
325                    $this->config[$section],
326                    $config[$section]
327                );
328            }
329        }
330
331        $this->config['base']['farmdir'] = trim($this->config['base']['farmdir']);
332        $this->config['base']['farmhost'] = strtolower(trim($this->config['base']['farmhost']));
333    }
334
335}
336
337// initialize it globally
338if(!defined('DOKU_UNITTEST')) {
339    global $FARMCORE;
340    $FARMCORE = new DokuWikiFarmCore();
341}
342