1<?php
2
3/**
4 * Load all internal libraries and setup class autoloader
5 *
6 * @author Andreas Gohr <andi@splitbrain.org>
7 */
8
9namespace dokuwiki;
10
11use dokuwiki\Extension\PluginController;
12
13return new class {
14    /** @var string[] Common libraries that are always loaded */
15    protected array $commonLibs = [
16        'defines.php',
17        'actions.php',
18        'changelog.php',
19        'common.php',
20        'confutils.php',
21        'pluginutils.php',
22        'form.php',
23        'fulltext.php',
24        'html.php',
25        'httputils.php',
26        'indexer.php',
27        'infoutils.php',
28        'io.php',
29        'mail.php',
30        'media.php',
31        'pageutils.php',
32        'parserutils.php',
33        'search.php',
34        'template.php',
35        'toolbar.php',
36        'utf8.php',
37        'auth.php',
38        'compatibility.php',
39        'deprecated.php',
40        'legacy.php',
41    ];
42
43    /** @var string[] Classname to file mappings */
44    protected array $fixedClassNames = [
45        'Diff' => 'DifferenceEngine.php',
46        'UnifiedDiffFormatter' => 'DifferenceEngine.php',
47        'TableDiffFormatter' => 'DifferenceEngine.php',
48        'cache' => 'cache.php',
49        'cache_parser' => 'cache.php',
50        'cache_instructions' => 'cache.php',
51        'cache_renderer' => 'cache.php',
52        'Input' => 'Input.class.php',
53        'JpegMeta' => 'JpegMeta.php',
54        'SimplePie' => 'SimplePie.php',
55        'FeedParser' => 'FeedParser.php',
56        'SafeFN' => 'SafeFN.class.php',
57        'Mailer' => 'Mailer.class.php',
58        'Doku_Handler' => 'parser/handler.php',
59        'Doku_Renderer' => 'parser/renderer.php',
60        'Doku_Renderer_xhtml' => 'parser/xhtml.php',
61        'Doku_Renderer_code' => 'parser/code.php',
62        'Doku_Renderer_xhtmlsummary' => 'parser/xhtmlsummary.php',
63        'Doku_Renderer_metadata' => 'parser/metadata.php'
64    ];
65
66    /**
67     * Load common libs and register autoloader
68     */
69    public function __construct()
70    {
71        require_once(DOKU_INC . 'vendor/autoload.php');
72        spl_autoload_register([$this, 'autoload']);
73        $this->loadCommonLibs();
74    }
75
76    /**
77     * require all the common libraries
78     *
79     * @return true
80     */
81    public function loadCommonLibs()
82    {
83        foreach ($this->commonLibs as $lib) {
84            require_once(DOKU_INC . 'inc/' . $lib);
85        }
86        return true;
87    }
88
89    /**
90     * spl_autoload_register callback
91     *
92     * @param string $className
93     * @return bool
94     */
95    public function autoload($className)
96    {
97        // namespace to directory conversion
98        $classPath = str_replace('\\', '/', $className);
99
100        return $this->autoloadFixedClass($className)
101            || $this->autoloadTestMockClass($classPath)
102            || $this->autoloadTestClass($classPath)
103            || $this->autoloadPluginClass($classPath)
104            || $this->autoloadTemplateClass($classPath)
105            || $this->autoloadCoreClass($classPath)
106            || $this->autoloadNamedPluginClass($className);
107    }
108
109    /**
110     * Check if the class is one of the fixed names
111     *
112     * @param string $className
113     * @return bool true if the class was loaded, false otherwise
114     */
115    protected function autoloadFixedClass($className)
116    {
117        if (isset($this->fixedClassNames[$className])) {
118            require($this->fixedClassNames[$className]);
119            return true;
120        }
121        return false;
122    }
123
124    /**
125     * Check if the class is a test mock class
126     *
127     * @param string $classPath The class name using forward slashes as namespace separators
128     * @return bool true if the class was loaded, false otherwise
129     */
130    protected function autoloadTestMockClass($classPath)
131    {
132        if ($this->prefixStrip($classPath, 'dokuwiki/test/mock/')) {
133            $file = DOKU_INC . '_test/mock/' . $classPath . '.php';
134            if (file_exists($file)) {
135                require $file;
136                return true;
137            }
138        }
139        return false;
140    }
141
142    /**
143     * Check if the class is a test mock class
144     *
145     * @param string $classPath The class name using forward slashes as namespace separators
146     * @return bool true if the class was loaded, false otherwise
147     */
148    protected function autoloadTestClass($classPath)
149    {
150        if ($this->prefixStrip($classPath, 'dokuwiki/test/')) {
151            $file = DOKU_INC . '_test/tests/' . $classPath . '.php';
152            if (file_exists($file)) {
153                require $file;
154                return true;
155            }
156        }
157        return false;
158    }
159
160    /**
161     * Check if the class is a namespaced plugin class
162     *
163     * @param string $classPath The class name using forward slashes as namespace separators
164     * @return bool true if the class was loaded, false otherwise
165     */
166    protected function autoloadPluginClass($classPath)
167    {
168        global $plugin_controller;
169
170        if ($this->prefixStrip($classPath, 'dokuwiki/plugin/')) {
171            $classPath = str_replace('/test/', '/_test/', $classPath); // no underscore in test namespace
172            $file = DOKU_PLUGIN . $classPath . '.php';
173            if (file_exists($file)) {
174                $plugin = substr($classPath, 0, strpos($classPath, '/'));
175                // don't load disabled plugin classes (only if plugin controller is available)
176                if (!defined('DOKU_UNITTEST') && $plugin_controller && plugin_isdisabled($plugin)) return false;
177
178                try {
179                    require $file;
180                } catch (\Throwable $e) {
181                    ErrorHandler::showExceptionMsg($e, "Error loading plugin $plugin");
182                }
183                return true;
184            }
185        }
186        return false;
187    }
188
189    /**
190     * Check if the class is a namespaced template class
191     *
192     * @param string $classPath The class name using forward slashes as namespace separators
193     * @return bool true if the class was loaded, false otherwise
194     */
195    protected function autoloadTemplateClass($classPath)
196    {
197        // template namespace
198        if ($this->prefixStrip($classPath, 'dokuwiki/template/')) {
199            $classPath = str_replace('/test/', '/_test/', $classPath); // no underscore in test namespace
200            $file = DOKU_INC . 'lib/tpl/' . $classPath . '.php';
201            if (file_exists($file)) {
202                $template = substr($classPath, 0, strpos($classPath, '/'));
203
204                try {
205                    require $file;
206                } catch (\Throwable $e) {
207                    ErrorHandler::showExceptionMsg($e, "Error loading template $template");
208                }
209                return true;
210            }
211        }
212        return false;
213    }
214
215    /**
216     * Check if the class is a namespaced DokuWiki core class
217     *
218     * @param string $classPath The class name using forward slashes as namespace separators
219     * @return bool true if the class was loaded, false otherwise
220     */
221    protected function autoloadCoreClass($classPath)
222    {
223        if ($this->prefixStrip($classPath, 'dokuwiki/')) {
224            $file = DOKU_INC . 'inc/' . $classPath . '.php';
225            if (file_exists($file)) {
226                require $file;
227                return true;
228            }
229        }
230        return false;
231    }
232
233    /**
234     * Check if the class is a un-namespaced plugin class following our naming scheme
235     *
236     * @param string $className
237     * @return bool true if the class was loaded, false otherwise
238     */
239    protected function autoloadNamedPluginClass($className)
240    {
241        global $plugin_controller;
242
243        if (
244            preg_match(
245                '/^(' . implode('|', PluginController::PLUGIN_TYPES) . ')_plugin_(' .
246                DOKU_PLUGIN_NAME_REGEX .
247                ')(?:_([^_]+))?$/',
248                $className,
249                $m
250            )
251        ) {
252            $c = ((count($m) === 4) ? "/{$m[3]}" : '');
253            $plg = DOKU_PLUGIN . "{$m[2]}/{$m[1]}$c.php";
254            if (file_exists($plg)) {
255                // don't load disabled plugin classes (only if plugin controller is available)
256                if (!defined('DOKU_UNITTEST') && $plugin_controller && plugin_isdisabled($m[2])) return false;
257                try {
258                    require $plg;
259                } catch (\Throwable $e) {
260                    ErrorHandler::showExceptionMsg($e, "Error loading plugin {$m[2]}");
261                }
262            }
263            return true;
264        }
265        return false;
266    }
267
268    /**
269     * Check if the given string starts with the given prefix and strip it
270     *
271     * @param string $string
272     * @param string $prefix
273     * @return bool true if the prefix was found and stripped, false otherwise
274     */
275    protected function prefixStrip(&$string, $prefix)
276    {
277        if (str_starts_with($string, $prefix)) {
278            $string = substr($string, strlen($prefix));
279            return true;
280        }
281        return false;
282    }
283};
284