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