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