1<?php 2namespace TYPO3\CMS\Core\Core; 3 4/* 5 * This file is part of the TYPO3 CMS project. 6 * 7 * It is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License, either version 2 9 * of the License, or any later version. 10 * 11 * For the full copyright and license information, please read the 12 * LICENSE.txt file that was distributed with this source code. 13 * 14 * The TYPO3 project - inspiring people to share! 15 */ 16 17use Composer\Autoload\ClassLoader; 18use Composer\Autoload\ClassMapGenerator; 19use TYPO3\CMS\Core\Package\PackageInterface; 20use TYPO3\CMS\Core\Utility\GeneralUtility; 21 22/** 23 * Generates class loading information (class maps, class aliases etc.) and writes it to files 24 * for further inclusion in the bootstrap 25 * @internal 26 */ 27class ClassLoadingInformationGenerator 28{ 29 /** 30 * @var PackageInterface[] 31 */ 32 protected $activeExtensionPackages; 33 34 /** 35 * @var ClassLoader 36 */ 37 protected $classLoader; 38 39 /** 40 * @var string 41 */ 42 protected $installationRoot; 43 44 /** 45 * @var bool 46 */ 47 protected $isDevMode; 48 49 /** 50 * @param ClassLoader $classLoader 51 * @param array $activeExtensionPackages 52 * @param string $installationRoot 53 * @param bool $isDevMode 54 */ 55 public function __construct(ClassLoader $classLoader, array $activeExtensionPackages, $installationRoot, $isDevMode = false) 56 { 57 $this->classLoader = $classLoader; 58 $this->activeExtensionPackages = $activeExtensionPackages; 59 $this->installationRoot = $installationRoot; 60 $this->isDevMode = $isDevMode; 61 } 62 63 /** 64 * Returns class loading information for a single package 65 * 66 * @param PackageInterface $package The package to generate the class loading info for 67 * @param bool $useRelativePaths If set to TRUE, make the path relative to the current TYPO3 public web path 68 * @return array 69 */ 70 public function buildClassLoadingInformationForPackage(PackageInterface $package, $useRelativePaths = false) 71 { 72 $classMap = []; 73 $psr4 = []; 74 $packagePath = $package->getPackagePath(); 75 $manifest = $package->getValueFromComposerManifest(); 76 77 if (empty($manifest->autoload)) { 78 // Legacy mode: Scan the complete extension directory for class files 79 $classMap = $this->createClassMap($packagePath, $useRelativePaths, !$this->isDevMode); 80 } else { 81 $autoloadPsr4 = $this->getAutoloadSectionFromManifest($manifest, 'psr-4'); 82 if (!empty($autoloadPsr4)) { 83 foreach ($autoloadPsr4 as $namespacePrefix => $paths) { 84 foreach ((array)$paths as $path) { 85 $namespacePath = $packagePath . $path; 86 $namespaceRealPath = realpath($namespacePath); 87 if ($useRelativePaths) { 88 $psr4[$namespacePrefix][] = $this->makePathRelative($namespacePath, $namespaceRealPath); 89 } else { 90 $psr4[$namespacePrefix][] = $namespacePath; 91 } 92 if (!empty($namespaceRealPath) && is_dir($namespaceRealPath)) { 93 // Add all prs-4 classes to the class map for improved class loading performance 94 $classMap = array_merge($classMap, $this->createClassMap($namespacePath, $useRelativePaths, false, $namespacePrefix)); 95 } 96 } 97 } 98 } 99 $autoloadClassmap = $this->getAutoloadSectionFromManifest($manifest, 'classmap'); 100 if (!empty($autoloadClassmap)) { 101 foreach ($autoloadClassmap as $path) { 102 $classMap = array_merge($classMap, $this->createClassMap($packagePath . $path, $useRelativePaths)); 103 } 104 } 105 } 106 107 return ['classMap' => $classMap, 'psr-4' => $psr4]; 108 } 109 110 /** 111 * Fetches class loading info from the according section from the manifest file. 112 * Development information will be extracted and merged as well. 113 * 114 * @param \stdClass $manifest 115 * @param string $section 116 * @return array 117 */ 118 protected function getAutoloadSectionFromManifest($manifest, $section) 119 { 120 $finalAutoloadSection = []; 121 $autoloadDefinition = json_decode(json_encode($manifest->autoload), true); 122 if (!empty($autoloadDefinition[$section]) && is_array($autoloadDefinition[$section])) { 123 $finalAutoloadSection = $autoloadDefinition[$section]; 124 } 125 if ($this->isDevMode) { 126 if (isset($manifest->{'autoload-dev'})) { 127 $autoloadDefinitionDev = json_decode(json_encode($manifest->{'autoload-dev'}), true); 128 if (!empty($autoloadDefinitionDev[$section]) && is_array($autoloadDefinitionDev[$section])) { 129 $finalAutoloadSection = array_merge($finalAutoloadSection, $autoloadDefinitionDev[$section]); 130 } 131 } 132 } 133 134 return $finalAutoloadSection; 135 } 136 137 /** 138 * Creates a class map for a given (absolute) path 139 * 140 * @param string $classesPath 141 * @param bool $useRelativePaths 142 * @param bool $ignorePotentialTestClasses 143 * @param string $namespace 144 * @return array 145 */ 146 protected function createClassMap($classesPath, $useRelativePaths = false, $ignorePotentialTestClasses = false, $namespace = null) 147 { 148 $classMap = []; 149 $blacklistExpression = null; 150 if ($ignorePotentialTestClasses) { 151 $blacklistPathPrefix = realpath($classesPath); 152 $blacklistPathPrefix = str_replace('\\', '/', $blacklistPathPrefix); 153 $blacklistExpression = "{($blacklistPathPrefix/tests/|$blacklistPathPrefix/Tests/|$blacklistPathPrefix/Resources/|$blacklistPathPrefix/res/|$blacklistPathPrefix/class.ext_update.php)}"; 154 } 155 foreach (ClassMapGenerator::createMap($classesPath, $blacklistExpression, null, $namespace) as $class => $path) { 156 if ($useRelativePaths) { 157 $classMap[$class] = $this->makePathRelative($classesPath, $path); 158 } else { 159 $classMap[$class] = $path; 160 } 161 } 162 return $classMap; 163 } 164 165 /** 166 * Returns class alias map for given package 167 * 168 * @param PackageInterface $package The package to generate the class alias info for 169 * @throws \TYPO3\CMS\Core\Error\Exception 170 * @return array 171 */ 172 public function buildClassAliasMapForPackage(PackageInterface $package) 173 { 174 $aliasToClassNameMapping = []; 175 $classNameToAliasMapping = []; 176 $possibleClassAliasFiles = []; 177 $manifest = $package->getValueFromComposerManifest(); 178 if (!empty($manifest->extra->{'typo3/class-alias-loader'}->{'class-alias-maps'})) { 179 $possibleClassAliasFiles = $manifest->extra->{'typo3/class-alias-loader'}->{'class-alias-maps'}; 180 if (!is_array($possibleClassAliasFiles)) { 181 throw new \TYPO3\CMS\Core\Error\Exception('"typo3/class-alias-loader"/"class-alias-maps" must return an array!', 1444142481); 182 } 183 } else { 184 $possibleClassAliasFiles[] = 'Migrations/Code/ClassAliasMap.php'; 185 } 186 $packagePath = $package->getPackagePath(); 187 foreach ($possibleClassAliasFiles as $possibleClassAliasFile) { 188 $possiblePathToClassAliasFile = $packagePath . $possibleClassAliasFile; 189 if (file_exists($possiblePathToClassAliasFile)) { 190 $packageAliasMap = require $possiblePathToClassAliasFile; 191 if (!is_array($packageAliasMap)) { 192 throw new \TYPO3\CMS\Core\Error\Exception('"class alias maps" must return an array', 1422625075); 193 } 194 foreach ($packageAliasMap as $aliasClassName => $className) { 195 $lowerCasedAliasClassName = strtolower($aliasClassName); 196 $aliasToClassNameMapping[$lowerCasedAliasClassName] = $className; 197 $classNameToAliasMapping[$className][$lowerCasedAliasClassName] = $lowerCasedAliasClassName; 198 } 199 } 200 } 201 202 return ['aliasToClassNameMapping' => $aliasToClassNameMapping, 'classNameToAliasMapping' => $classNameToAliasMapping]; 203 } 204 205 /** 206 * Generate the class map file 207 * @return string[] 208 * @internal 209 */ 210 public function buildAutoloadInformationFiles() 211 { 212 $psr4File = $classMapFile = <<<EOF 213<?php 214 215// autoload_classmap.php @generated by TYPO3 216 217\$typo3InstallDir = \TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/'; 218 219return array( 220 221EOF; 222 $classMap = []; 223 $psr4 = []; 224 foreach ($this->activeExtensionPackages as $package) { 225 $classLoadingInformation = $this->buildClassLoadingInformationForPackage($package, true); 226 $classMap = array_merge($classMap, $classLoadingInformation['classMap']); 227 $psr4 = array_merge($psr4, $classLoadingInformation['psr-4']); 228 } 229 230 ksort($classMap); 231 ksort($psr4); 232 foreach ($classMap as $class => $relativePath) { 233 $classMapFile .= sprintf(' %s => %s,', var_export($class, true), $this->getPathCode($relativePath)) . "\n"; 234 } 235 $classMapFile .= ");\n"; 236 237 foreach ($psr4 as $prefix => $relativePaths) { 238 $psr4File .= sprintf(' %s => array(%s),', var_export($prefix, true), implode(',', array_map([$this, 'getPathCode'], $relativePaths))) . "\n"; 239 } 240 $psr4File .= ");\n"; 241 242 return ['classMapFile' => $classMapFile, 'psr-4File' => $psr4File]; 243 } 244 245 /** 246 * Generate a relative path string from an absolute path within a give package path 247 * 248 * @param string $packagePath 249 * @param string $realPathOfClassFile 250 * @param bool $relativeToRoot 251 * @return string 252 */ 253 protected function makePathRelative($packagePath, $realPathOfClassFile, $relativeToRoot = true) 254 { 255 $realPathOfClassFile = GeneralUtility::fixWindowsFilePath($realPathOfClassFile); 256 $packageRealPath = GeneralUtility::fixWindowsFilePath(realpath($packagePath)); 257 $relativePackagePath = rtrim(substr($packagePath, strlen($this->installationRoot)), '/'); 258 if ($relativeToRoot) { 259 if ($realPathOfClassFile === $packageRealPath) { 260 $relativePathToClassFile = $relativePackagePath; 261 } else { 262 $relativePathToClassFile = $relativePackagePath . '/' . ltrim(substr($realPathOfClassFile, strlen($packageRealPath)), '/'); 263 } 264 } else { 265 $relativePathToClassFile = ltrim(substr($realPathOfClassFile, strlen($packageRealPath)), '/'); 266 } 267 268 return $relativePathToClassFile; 269 } 270 271 /** 272 * Generate a relative path string from a relative path 273 * 274 * @param string $relativePathToClassFile 275 * @return string 276 */ 277 protected function getPathCode($relativePathToClassFile) 278 { 279 return '$typo3InstallDir . ' . var_export($relativePathToClassFile, true); 280 } 281 282 /** 283 * Build class alias mapping file 284 * 285 * @return string 286 * @throws \Exception 287 * @internal 288 */ 289 public function buildClassAliasMapFile() 290 { 291 $aliasToClassNameMapping = []; 292 $classNameToAliasMapping = []; 293 foreach ($this->activeExtensionPackages as $package) { 294 $aliasMappingForPackage = $this->buildClassAliasMapForPackage($package); 295 $aliasToClassNameMapping = array_merge($aliasToClassNameMapping, $aliasMappingForPackage['aliasToClassNameMapping']); 296 $classNameToAliasMapping = array_merge($classNameToAliasMapping, $aliasMappingForPackage['classNameToAliasMapping']); 297 } 298 $exportArray = [ 299 'aliasToClassNameMapping' => $aliasToClassNameMapping, 300 'classNameToAliasMapping' => $classNameToAliasMapping 301 ]; 302 $fileContent = "<?php\nreturn "; 303 $fileContent .= var_export($exportArray, true); 304 $fileContent .= ";\n"; 305 return $fileContent; 306 } 307} 308