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