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        'JpegMeta' => 'JpegMeta.php',
53        'FeedParser' => 'FeedParser.php',
54        'SafeFN' => 'SafeFN.class.php',
55        'Mailer' => 'Mailer.class.php',
56        'Doku_Handler' => 'parser/handler.php',
57        'Doku_Renderer' => 'parser/renderer.php',
58        'Doku_Renderer_xhtml' => 'parser/xhtml.php',
59        'Doku_Renderer_code' => 'parser/code.php',
60        'Doku_Renderer_xhtmlsummary' => 'parser/xhtmlsummary.php',
61        'Doku_Renderer_metadata' => 'parser/metadata.php'
62    ];
63
64    /**
65     * Load common libs and register autoloader
66     */
67    public function __construct()
68    {
69        require_once(DOKU_INC . 'vendor/autoload.php');
70        spl_autoload_register([$this, 'autoload']);
71        $this->loadCommonLibs();
72    }
73
74    /**
75     * require all the common libraries
76     *
77     * @return true
78     */
79    public function loadCommonLibs()
80    {
81        foreach ($this->commonLibs as $lib) {
82            require_once(DOKU_INC . 'inc/' . $lib);
83        }
84        return true;
85    }
86
87    /**
88     * spl_autoload_register callback
89     *
90     * @param string $className
91     * @return bool
92     */
93    public function autoload($className)
94    {
95        // namespace to directory conversion
96        $classPath = str_replace('\\', '/', $className);
97
98        return $this->autoloadFixedClass($className)
99            || $this->autoloadTestMockClass($classPath)
100            || $this->autoloadTestClass($classPath)
101            || $this->autoloadPluginClass($classPath)
102            || $this->autoloadTemplateClass($classPath)
103            || $this->autoloadCoreClass($classPath)
104            || $this->autoloadNamedPluginClass($className);
105    }
106
107    /**
108     * Check if the class is one of the fixed names
109     *
110     * @param string $className
111     * @return bool true if the class was loaded, false otherwise
112     */
113    protected function autoloadFixedClass($className)
114    {
115        if (isset($this->fixedClassNames[$className])) {
116            require($this->fixedClassNames[$className]);
117            return true;
118        }
119        return false;
120    }
121
122    /**
123     * Check if the class is a test mock class
124     *
125     * @param string $classPath The class name using forward slashes as namespace separators
126     * @return bool true if the class was loaded, false otherwise
127     */
128    protected function autoloadTestMockClass($classPath)
129    {
130        if ($this->prefixStrip($classPath, 'dokuwiki/test/mock/')) {
131            $file = DOKU_INC . '_test/mock/' . $classPath . '.php';
132            if (file_exists($file)) {
133                require $file;
134                return true;
135            }
136        }
137        return false;
138    }
139
140    /**
141     * Check if the class is a test mock class
142     *
143     * @param string $classPath The class name using forward slashes as namespace separators
144     * @return bool true if the class was loaded, false otherwise
145     */
146    protected function autoloadTestClass($classPath)
147    {
148        if ($this->prefixStrip($classPath, 'dokuwiki/test/')) {
149            $file = DOKU_INC . '_test/tests/' . $classPath . '.php';
150            if (file_exists($file)) {
151                require $file;
152                return true;
153            }
154        }
155        return false;
156    }
157
158    /**
159     * Check if the class is a namespaced plugin class
160     *
161     * @param string $classPath The class name using forward slashes as namespace separators
162     * @return bool true if the class was loaded, false otherwise
163     */
164    protected function autoloadPluginClass($classPath)
165    {
166        global $plugin_controller;
167
168        if ($this->prefixStrip($classPath, 'dokuwiki/plugin/')) {
169            $classPath = str_replace('/test/', '/_test/', $classPath); // no underscore in test namespace
170            $file = DOKU_PLUGIN . $classPath . '.php';
171            if (file_exists($file)) {
172                $plugin = substr($classPath, 0, strpos($classPath, '/'));
173                // don't load disabled plugin classes (only if plugin controller is available)
174                if (!defined('DOKU_UNITTEST') && $plugin_controller && plugin_isdisabled($plugin)) return false;
175
176                try {
177                    require $file;
178                } catch (\Throwable $e) {
179                    ErrorHandler::showExceptionMsg($e, "Error loading plugin $plugin");
180                }
181                return true;
182            }
183        }
184        return false;
185    }
186
187    /**
188     * Check if the class is a namespaced template class
189     *
190     * @param string $classPath The class name using forward slashes as namespace separators
191     * @return bool true if the class was loaded, false otherwise
192     */
193    protected function autoloadTemplateClass($classPath)
194    {
195        // template namespace
196        if ($this->prefixStrip($classPath, 'dokuwiki/template/')) {
197            $classPath = str_replace('/test/', '/_test/', $classPath); // no underscore in test namespace
198            $file = DOKU_INC . 'lib/tpl/' . $classPath . '.php';
199            if (file_exists($file)) {
200                $template = substr($classPath, 0, strpos($classPath, '/'));
201
202                try {
203                    require $file;
204                } catch (\Throwable $e) {
205                    ErrorHandler::showExceptionMsg($e, "Error loading template $template");
206                }
207                return true;
208            }
209        }
210        return false;
211    }
212
213    /**
214     * Check if the class is a namespaced DokuWiki core class
215     *
216     * @param string $classPath The class name using forward slashes as namespace separators
217     * @return bool true if the class was loaded, false otherwise
218     */
219    protected function autoloadCoreClass($classPath)
220    {
221        if ($this->prefixStrip($classPath, 'dokuwiki/')) {
222            $file = DOKU_INC . 'inc/' . $classPath . '.php';
223            if (file_exists($file)) {
224                require $file;
225                return true;
226            }
227        }
228        return false;
229    }
230
231    /**
232     * Check if the class is a un-namespaced plugin class following our naming scheme
233     *
234     * @param string $className
235     * @return bool true if the class was loaded, false otherwise
236     */
237    protected function autoloadNamedPluginClass($className)
238    {
239        global $plugin_controller;
240
241        if (
242            preg_match(
243                '/^(' . implode('|', PluginController::PLUGIN_TYPES) . ')_plugin_(' .
244                DOKU_PLUGIN_NAME_REGEX .
245                ')(?:_([^_]+))?$/',
246                $className,
247                $m
248            )
249        ) {
250            $c = ((count($m) === 4) ? "/{$m[3]}" : '');
251            $plg = DOKU_PLUGIN . "{$m[2]}/{$m[1]}$c.php";
252            if (file_exists($plg)) {
253                // don't load disabled plugin classes (only if plugin controller is available)
254                if (!defined('DOKU_UNITTEST') && $plugin_controller && plugin_isdisabled($m[2])) return false;
255                try {
256                    require $plg;
257                } catch (\Throwable $e) {
258                    ErrorHandler::showExceptionMsg($e, "Error loading plugin {$m[2]}");
259                }
260            }
261            return true;
262        }
263        return false;
264    }
265
266    /**
267     * Check if the given string starts with the given prefix and strip it
268     *
269     * @param string $string
270     * @param string $prefix
271     * @return bool true if the prefix was found and stripped, false otherwise
272     */
273    protected function prefixStrip(&$string, $prefix)
274    {
275        if (str_starts_with($string, $prefix)) {
276            $string = substr($string, strlen($prefix));
277            return true;
278        }
279        return false;
280    }
281};
282