Class name => absolute file path. Empty when * the file is absent, unreadable, half-written, * or does not return an array. The caller treats * empty as "keep the map you already have". */ function abj404_autoloader_read_classmap($mapFile, $refreshBytecode = false) { if (!is_file($mapFile)) { return array(); } if ($refreshBytecode && ABJ_404_Solution_PhpRuntimeCapabilityAdapter::isFunctionAvailable('opcache_invalidate') && (!function_exists('abj404_opcache_api_is_restricted') || !abj404_opcache_api_is_restricted(ini_get('opcache.restrict_api'), __FILE__))) { ABJ_404_Solution_OpcacheAdapter::invalidate($mapFile, true); } try { $loadedMap = require $mapFile; } catch (\Throwable $readFailure) { // A refresh reads this file at the one moment another process is // REWRITING it, so `require` can compile a truncated copy and raise a // ParseError. Reporting no map lets the caller keep the working one; // the next miss re-reads the by-then-complete file. if (function_exists('abj404_logRuntimeWarning')) { abj404_logRuntimeWarning('abj404_autoloader: could not read ' . $mapFile, $readFailure); } return array(); } $normalizedMap = array(); if (is_array($loadedMap)) { foreach ($loadedMap as $mappedClass => $mappedFile) { if (is_string($mappedClass) && is_string($mappedFile)) { $normalizedMap[$mappedClass] = $mappedFile; } } } return $normalizedMap; } } if (!function_exists('abj404_autoloader')) { /** * @param string $class * @return void */ function abj404_autoloader($class) { // some people were having issues with possibly parent classes not being loaded before their children. $childParentMap = [ 'ABJ_404_Solution_FunctionsMBString' => 'ABJ_404_Solution_Functions', 'ABJ_404_Solution_FunctionsPreg' => 'ABJ_404_Solution_Functions', 'ABJ_404_Solution_MbStringAdapterMb' => 'ABJ_404_Solution_MbStringAdapter', 'ABJ_404_Solution_MbStringAdapterPreg' => 'ABJ_404_Solution_MbStringAdapter', 'ABJ_404_Solution_RegexHelperMb' => 'ABJ_404_Solution_RegexHelper', 'ABJ_404_Solution_RegexHelperPreg' => 'ABJ_404_Solution_RegexHelper', ]; // only pay attention if it's for us. don't bother for other things. if (substr($class, 0, 16) !== 'ABJ_404_Solution') { return; } // Use a deterministic classmap to avoid runtime glob() scans on real sites. /** @var array|null $abj404_autoLoaderClassMap */ static $abj404_autoLoaderClassMap = null; /** @var string $abj404_autoLoaderMapStamp Disk identity of the map above. */ static $abj404_autoLoaderMapStamp = ''; // Declared here rather than beside its first use: a `static` statement // rebinds the variable to static storage when it executes, so an // assignment placed before it (the refresh below invalidates this map) // would be silently discarded on every call after the first. /** @var array>|null $traitDependencies */ static $traitDependencies = null; /** @var string|null $mapFile */ static $mapFile = null; if ($mapFile === null) { $mapFile = dirname(__DIR__) . '/classmap.php'; } if ($abj404_autoLoaderClassMap === null) { $abj404_autoLoaderClassMap = abj404_autoloader_read_classmap($mapFile); $abj404_autoLoaderMapStamp = abj404_autoloader_classmap_stamp($mapFile); } if (!array_key_exists($class, $abj404_autoLoaderClassMap)) { // STALE-MAP REFRESH. A miss is normally the truth -- the class is not // ours -- but it is also exactly what a plugin self-update looks like // from inside a request that booted on the previous release: same // paths, new file contents, and a collaborator this map has never // heard of (production report 266, TableReadinessGate). Re-reading the // map is the only way to tell those two apart, and the stamp is what // keeps the re-read off the hot path: an ordinary miss costs one stat. $currentStamp = abj404_autoloader_classmap_stamp($mapFile); if ($currentStamp === '' || $currentStamp === $abj404_autoLoaderMapStamp) { return; } $refreshedMap = abj404_autoloader_read_classmap($mapFile, true); if ($refreshedMap === array()) { // Half-written or unreadable while the updater moves directories. // Keep the map we already have (and its stamp, so the next miss // tries again): trading a working map for an empty one would break // every remaining autoload in this request. return; } $abj404_autoLoaderClassMap = $refreshedMap; $abj404_autoLoaderMapStamp = $currentStamp; // Derived from the classmap, so it is stale for the same reason. $traitDependencies = null; if (!array_key_exists($class, $abj404_autoLoaderClassMap)) { return; } } // Composition dependency pre-check: parent facade classes depend on files // being present before they are loaded. Verify those files exist first so // a corrupt install can surface a degraded admin page instead of a fatal. // The class->collaborators map is DATA, loaded from an external file. if ($traitDependencies === null) { $mapDataFile = dirname(__DIR__) . '/data/autoloader-trait-dependencies.php'; $traitDependencyClasses = file_exists($mapDataFile) ? require $mapDataFile : array(); if (!is_array($traitDependencyClasses)) { $traitDependencyClasses = array(); } $traitDependencies = array(); foreach ($traitDependencyClasses as $hostClass => $dependencyClasses) { $traitDependencies[$hostClass] = array(); if (!is_array($dependencyClasses)) { continue; } foreach ($dependencyClasses as $dependencyClass) { if (is_string($dependencyClass) && isset($abj404_autoLoaderClassMap[$dependencyClass])) { $traitDependencies[$hostClass][] = $abj404_autoLoaderClassMap[$dependencyClass]; } } } } if (isset($traitDependencies[$class])) { foreach ($traitDependencies[$class] as $dependencyFile) { if (!file_exists($dependencyFile)) { abj404_record_missing_file($dependencyFile); // Don't load the parent class: the compile-time fatal is uncatchable. return; } } } // Ensure the parent class is loaded first. if (array_key_exists($class, $childParentMap)) { $parentClass = $childParentMap[$class]; if (!class_exists($parentClass, false) && array_key_exists($parentClass, $abj404_autoLoaderClassMap)) { $parentFile = $abj404_autoLoaderClassMap[$parentClass]; if (!file_exists($parentFile)) { abj404_record_missing_file($parentFile); return; } require_once $parentFile; } } $classFile = $abj404_autoLoaderClassMap[$class]; if (!file_exists($classFile)) { abj404_record_missing_file($classFile); return; } require_once $classFile; } }