1 <?php
2
3 4 5 6 7 8 9 10 11 12
13 class rex_autoload
14 {
15 16 17
18 protected static $composerLoader;
19
20 protected static $registered = false;
21 protected static $cacheFile = null;
22 protected static $cacheChanged = false;
23 protected static $reloaded = false;
24 protected static $dirs = [];
25 protected static $addedDirs = [];
26 protected static $classes = [];
27
28 29 30
31 public static function register()
32 {
33 if (self::$registered) {
34 return;
35 }
36
37 ini_set('unserialize_callback_func', 'spl_autoload_call');
38
39 if (!self::$composerLoader) {
40 self::$composerLoader = require rex_path::core('vendor/autoload.php');
41
42 self::$composerLoader->unregister();
43
44 self::$composerLoader->setClassMapAuthoritative(true);
45 }
46
47 if (false === spl_autoload_register([self::class, 'autoload'])) {
48 throw new Exception(sprintf('Unable to register %s::autoload as an autoloading method.', self::class));
49 }
50
51 self::$cacheFile = rex_path::coreCache('autoload.cache');
52 self::loadCache();
53 register_shutdown_function([self::class, 'saveCache']);
54
55 self::$registered = true;
56 }
57
58 59 60
61 public static function unregister()
62 {
63 spl_autoload_unregister([self::class, 'autoload']);
64 self::$registered = false;
65 }
66
67 68 69 70 71 72 73
74 public static function autoload($class)
75 {
76
77 if (self::classExists($class)) {
78 return true;
79 }
80
81 $force = false;
82 $lowerClass = strtolower($class);
83 if (isset(self::$classes[$lowerClass])) {
84 $path = rex_path::base(self::$classes[$lowerClass]);
85
86 if (@include_once $path) {
87 if (self::classExists($class)) {
88 return true;
89 }
90 }
91
92
93
94 $force = true;
95 unset(self::$classes[$lowerClass]);
96 self::$cacheChanged = true;
97 }
98
99
100 if (self::$composerLoader->loadClass($class) && self::classExists($class)) {
101 return true;
102 }
103
104
105
106 if (
107 (!self::$reloaded || $force) &&
108 (rex::isSetup() || rex::getConsole() || rex::isDebugMode() || ($user = rex_backend_login::createUser()) && $user->isAdmin())
109 ) {
110 self::reload($force);
111 return self::autoload($class);
112 }
113
114 return false;
115 }
116
117 118 119 120 121 122 123
124 private static function classExists($class)
125 {
126 return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
127 }
128
129 130 131
132 private static function loadCache()
133 {
134 if (!self::$cacheFile || !($cache = @file_get_contents(self::$cacheFile))) {
135 return;
136 }
137
138 list(self::$classes, self::$dirs) = json_decode($cache, true);
139 }
140
141 142 143
144 public static function saveCache()
145 {
146 if (!self::$cacheChanged) {
147 return;
148 }
149
150
151
152 $error = error_get_last();
153 if (is_array($error) && in_array($error['type'], [E_USER_ERROR, E_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR, E_PARSE])) {
154 return;
155 }
156
157
158 foreach (self::$dirs as $dir => $files) {
159 if (!in_array($dir, self::$addedDirs)) {
160 unset(self::$dirs[$dir]);
161 }
162 }
163
164 if (!rex_file::putCache(self::$cacheFile, [self::$classes, self::$dirs])) {
165 throw new Exception("Unable to write autoload cachefile '" . self::$cacheFile . "'!");
166 }
167 self::$cacheChanged = false;
168 }
169
170 171 172 173 174
175 public static function reload($force = false)
176 {
177 if ($force) {
178 self::$classes = [];
179 self::$dirs = [];
180 }
181 foreach (self::$addedDirs as $dir) {
182 self::_addDirectory($dir);
183 }
184 self::$reloaded = true;
185 }
186
187 188 189
190 public static function removeCache()
191 {
192 rex_file::delete(self::$cacheFile);
193 }
194
195 196 197 198 199
200 public static function addDirectory($dir)
201 {
202 $dir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
203 $dir = rex_path::relative($dir);
204 if (in_array($dir, self::$addedDirs)) {
205 return;
206 }
207 self::$addedDirs[] = $dir;
208 if (!isset(self::$dirs[$dir])) {
209 self::_addDirectory($dir);
210 self::$cacheChanged = true;
211 }
212 }
213
214 215 216 217 218
219 public static function getClasses()
220 {
221 return array_keys(self::$classes);
222 }
223
224 225 226
227 private static function _addDirectory($dir)
228 {
229 $dirPath = rex_path::base($dir);
230
231 if (!is_dir($dirPath)) {
232 return;
233 }
234
235 if (!isset(self::$dirs[$dir])) {
236 self::$dirs[$dir] = [];
237 }
238 $files = self::$dirs[$dir];
239 $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirPath, RecursiveDirectoryIterator::SKIP_DOTS));
240 foreach ($iterator as $path => $file) {
241
242 if (!$file->isFile() || !in_array($file->getExtension(), ['php', 'inc'])) {
243 continue;
244 }
245
246 $file = rex_path::relative($path);
247 unset($files[$file]);
248 $checksum = filemtime($path);
249 if (isset(self::$dirs[$dir][$file]) && self::$dirs[$dir][$file] === $checksum) {
250 continue;
251 }
252 self::$dirs[$dir][$file] = $checksum;
253 self::$cacheChanged = true;
254
255 $classes = self::findClasses($path);
256 foreach ($classes as $class) {
257 $class = strtolower($class);
258 if (!isset(self::$classes[$class])) {
259 self::$classes[$class] = $file;
260 }
261 }
262 }
263 foreach ($files as $file) {
264 unset(self::$dirs[$file]);
265 self::$cacheChanged = true;
266 }
267 }
268
269 270 271 272 273 274 275 276 277 278 279 280
281 private static function findClasses($path)
282 {
283 $extraTypes = PHP_VERSION_ID < 50400 ? '' : '|trait';
284 if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) {
285 $extraTypes .= '|enum';
286 }
287
288
289
290 $contents = @php_strip_whitespace($path);
291 if (!$contents) {
292 if (!file_exists($path)) {
293 $message = 'File at "%s" does not exist, check your classmap definitions';
294 } elseif (!is_readable($path)) {
295 $message = 'File at "%s" is not readable, check its permissions';
296 } elseif ('' === trim(file_get_contents($path))) {
297
298 return [];
299 } else {
300 $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
301 }
302 $error = error_get_last();
303 if (isset($error['message'])) {
304 $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
305 }
306 throw new \RuntimeException(sprintf($message, $path));
307 }
308
309
310 if (!preg_match('{\b(?:class|interface'.$extraTypes.')\s}i', $contents)) {
311 return [];
312 }
313
314
315 $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
316
317 $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
318
319 if (substr($contents, 0, 2) !== '<?') {
320 $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
321 if ($replacements === 0) {
322 return [];
323 }
324 }
325
326 $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
327
328 $pos = strrpos($contents, '?>');
329 if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
330 $contents = substr($contents, 0, $pos);
331 }
332
333 if (preg_match('{(<\?)(?!(php|hh))}i', $contents)) {
334 $contents = preg_replace('{//.* | /\*(?:[^*]++|\*(?!/))*\*/}x', '', $contents);
335 }
336
337 preg_match_all('{
338 (?:
339 \b(?<![\$:>])(?P<type>class|interface'.$extraTypes.') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
340 | \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
341 )
342 }ix', $contents, $matches);
343
344 $classes = [];
345 $namespace = '';
346
347 for ($i = 0, $len = count($matches['type']); $i < $len; ++$i) {
348 if (!empty($matches['ns'][$i])) {
349 $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\';
350 } else {
351 $name = $matches['name'][$i];
352
353 if ($name === 'extends' || $name === 'implements') {
354 continue;
355 }
356 if ($name[0] === ':') {
357
358 $name = 'xhp'.substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
359 } elseif ($matches['type'][$i] === 'enum') {
360
361
362
363
364 $name = rtrim($name, ':');
365 }
366 $classes[] = ltrim($namespace . $name, '\\');
367 }
368 }
369
370 return $classes;
371 }
372 }
373