1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Core\Core;
17
18use Composer\Autoload\ClassLoader;
19use TYPO3\ClassAliasLoader\ClassAliasMap;
20use TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent;
21use TYPO3\CMS\Core\Package\Event\AfterPackageDeactivationEvent;
22use TYPO3\CMS\Core\Package\PackageInterface;
23use TYPO3\CMS\Core\Package\PackageManager;
24use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26/**
27 * Get and manipulate class loading information, only necessary/in use
28 * when TYPO3 is not purely set up by composer but when e.g. extensions are installed via the extension manager
29 * by utilizing the composer class loader and adding more information built by the ClassLoadingInformationGenerator
30 * class.
31 *
32 * @internal
33 */
34class ClassLoadingInformation
35{
36    /**
37     * Base directory storing all autoload information
38     */
39    const AUTOLOAD_INFO_DIR = 'autoload/';
40
41    /**
42     * Base directory storing all autoload information in testing context
43     */
44    const AUTOLOAD_INFO_DIR_TESTS = 'autoload-tests/';
45
46    /**
47     * Name of file that contains all classes-filename mappings
48     */
49    const AUTOLOAD_CLASSMAP_FILENAME = 'autoload_classmap.php';
50
51    /**
52     * Name of file that contains all PSR4 mappings, fetched from the composer.json files of extensions
53     */
54    const AUTOLOAD_PSR4_FILENAME = 'autoload_psr4.php';
55
56    /**
57     * Name of file that contains all class alias mappings
58     */
59    const AUTOLOAD_CLASSALIASMAP_FILENAME = 'autoload_classaliasmap.php';
60
61    /**
62     * @var ClassLoader
63     */
64    protected static $classLoader;
65
66    /**
67     * Sets the package manager instance
68     *
69     * @param ClassLoader $classLoader
70     * @internal
71     */
72    public static function setClassLoader(ClassLoader $classLoader)
73    {
74        static::$classLoader = $classLoader;
75    }
76
77    /**
78     * Checks if the autoload_classmap.php exists and we are not in testing context.
79     * Used to see if the ClassLoadingInformationGenerator should be called.
80     *
81     * @return bool
82     */
83    public static function isClassLoadingInformationAvailable()
84    {
85        return file_exists(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME);
86    }
87
88    /**
89     * Puts all information compiled by the ClassLoadingInformationGenerator to files
90     */
91    public static function dumpClassLoadingInformation()
92    {
93        self::ensureAutoloadInfoDirExists();
94        $composerClassLoader = static::getClassLoader();
95        $activeExtensionPackages = static::getActiveExtensionPackages();
96
97        /** @var ClassLoadingInformationGenerator  $generator */
98        $generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class, $composerClassLoader, $activeExtensionPackages, Environment::getPublicPath() . '/', self::isTestingContext());
99        $classInfoFiles = $generator->buildAutoloadInformationFiles();
100        GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME, $classInfoFiles['classMapFile']);
101        GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_PSR4_FILENAME, $classInfoFiles['psr-4File']);
102
103        $classAliasMapFile = $generator->buildClassAliasMapFile();
104        GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSALIASMAP_FILENAME, $classAliasMapFile);
105    }
106
107    /**
108     * @param AfterPackageDeactivationEvent $event
109     * @internal
110     */
111    public static function updateClassLoadingInformationAfterPackageDeactivation(AfterPackageDeactivationEvent $event): void
112    {
113        if (Environment::isComposerMode()) {
114            return;
115        }
116        static::dumpClassLoadingInformation();
117    }
118
119    /**
120     * @param AfterPackageActivationEvent $event
121     * @internal
122     */
123    public static function updateClassLoadingInformationAfterPackageActivation(AfterPackageActivationEvent $event): void
124    {
125        if (Environment::isComposerMode()) {
126            return;
127        }
128        static::dumpClassLoadingInformation();
129    }
130
131    /**
132     * Registers the class aliases, the class maps and the PSR4 prefixes previously identified by
133     * the ClassLoadingInformationGenerator during runtime.
134     */
135    public static function registerClassLoadingInformation()
136    {
137        $composerClassLoader = static::getClassLoader();
138
139        $dynamicClassAliasMapFile = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSALIASMAP_FILENAME;
140        if (file_exists($dynamicClassAliasMapFile)) {
141            $classAliasMap = require $dynamicClassAliasMapFile;
142            if (is_array($classAliasMap) && !empty($classAliasMap['aliasToClassNameMapping']) && !empty($classAliasMap['classNameToAliasMapping'])) {
143                ClassAliasMap::addAliasMap($classAliasMap);
144            }
145        }
146
147        $dynamicClassMapFile = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME;
148        if (file_exists($dynamicClassMapFile)) {
149            $classMap = require $dynamicClassMapFile;
150            if (!empty($classMap) && is_array($classMap)) {
151                $composerClassLoader->addClassMap($classMap);
152            }
153        }
154
155        $dynamicPsr4File = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_PSR4_FILENAME;
156        if (file_exists($dynamicPsr4File)) {
157            $psr4 = require $dynamicPsr4File;
158            if (is_array($psr4)) {
159                foreach ($psr4 as $prefix => $paths) {
160                    $composerClassLoader->setPsr4($prefix, $paths);
161                }
162            }
163        }
164    }
165
166    /**
167     * Sets class loading information for a package for the current web request
168     *
169     * @param PackageInterface $package
170     * @throws \TYPO3\CMS\Core\Error\Exception
171     */
172    public static function registerTransientClassLoadingInformationForPackage(PackageInterface $package)
173    {
174        $composerClassLoader = static::getClassLoader();
175        $activeExtensionPackages = static::getActiveExtensionPackages();
176
177        /** @var ClassLoadingInformationGenerator  $generator */
178        $generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class, $composerClassLoader, $activeExtensionPackages, Environment::getPublicPath() . '/', self::isTestingContext());
179
180        $classInformation = $generator->buildClassLoadingInformationForPackage($package);
181        $composerClassLoader->addClassMap($classInformation['classMap']);
182        foreach ($classInformation['psr-4'] as $prefix => $paths) {
183            $composerClassLoader->setPsr4($prefix, $paths);
184        }
185        $classAliasMap = $generator->buildClassAliasMapForPackage($package);
186        if (is_array($classAliasMap) && !empty($classAliasMap['aliasToClassNameMapping']) && !empty($classAliasMap['classNameToAliasMapping'])) {
187            ClassAliasMap::addAliasMap($classAliasMap);
188        }
189    }
190
191    /**
192     * @return string
193     */
194    protected static function getClassLoadingInformationDirectory()
195    {
196        if (self::isTestingContext()) {
197            return Environment::getLegacyConfigPath() . '/' . self::AUTOLOAD_INFO_DIR_TESTS;
198        }
199        return Environment::getLegacyConfigPath() . '/' . self::AUTOLOAD_INFO_DIR;
200    }
201
202    /**
203     * Get class name for alias
204     *
205     * @param string $alias
206     * @return mixed
207     */
208    public static function getClassNameForAlias($alias)
209    {
210        return ClassAliasMap::getClassNameForAlias($alias);
211    }
212
213    /**
214     * Ensures the defined path for class information files exists
215     * And clears it in case we're in testing context
216     */
217    protected static function ensureAutoloadInfoDirExists()
218    {
219        $autoloadInfoDir = self::getClassLoadingInformationDirectory();
220        if (!file_exists($autoloadInfoDir)) {
221            GeneralUtility::mkdir_deep($autoloadInfoDir);
222        }
223    }
224
225    /**
226     * Internal method calling the bootstrap to fetch the composer class loader
227     *
228     * @return ClassLoader
229     * @throws \TYPO3\CMS\Core\Exception
230     */
231    protected static function getClassLoader()
232    {
233        return static::$classLoader;
234    }
235
236    /**
237     * Internal method calling the bootstrap to get application context information
238     *
239     * @return bool
240     * @throws \TYPO3\CMS\Core\Exception
241     */
242    protected static function isTestingContext()
243    {
244        return Environment::getContext()->isTesting();
245    }
246
247    /**
248     * Get all packages except the protected ones, as they are covered already
249     *
250     * @return PackageInterface[]
251     */
252    protected static function getActiveExtensionPackages()
253    {
254        $activeExtensionPackages = [];
255        $packageManager = GeneralUtility::makeInstance(PackageManager::class);
256        foreach ($packageManager->getActivePackages() as $package) {
257            if ($package->getValueFromComposerManifest('type') === 'typo3-cms-framework') {
258                // Skip all core packages as the class loading info is prepared for them already
259                continue;
260            }
261            $activeExtensionPackages[] = $package;
262        }
263        return $activeExtensionPackages;
264    }
265}
266