1 <?php
2 
3 /**
4  * Load all internal libraries and setup class autoloader
5  *
6  * @author Andreas Gohr <andi@splitbrain.org>
7  */
8 
9 namespace dokuwiki;
10 
11 use dokuwiki\Extension\PluginController;
12 
13 return 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