[TASK] Move publicly accessible files to typo3temp/assets/
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / Action / Ajax / ExtensionCompatibilityTester.php
1 <?php
2 namespace TYPO3\CMS\Install\Controller\Action\Ajax;
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
17 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Load Extensions
22 *
23 * The idea is to load ext_localconf and ext_tables of extensions one-by-one
24 * until one of those files throws a fatal. The javascript will then recognise
25 * the fatal and initiates another run that will leave out the fataling extension
26 * to check the rest.
27 */
28 class ExtensionCompatibilityTester extends AbstractAjaxAction
29 {
30 /**
31 * Store extension loading protocol
32 *
33 * @var string
34 */
35 protected $protocolFile = '';
36
37 /**
38 * Store errors that occurred during checks.
39 *
40 * @var string
41 */
42 protected $errorProtocolFile = '';
43
44 /**
45 * Define whether to log errors to file or not.
46 *
47 * @var bool
48 */
49 protected $logError = false;
50
51 /**
52 * Construct this class
53 * set default protocol file location
54 */
55 public function __construct()
56 {
57 $this->protocolFile = PATH_site . 'typo3temp/assets/ExtensionCompatibilityTester.txt';
58 $this->errorProtocolFile = PATH_site . 'typo3temp/assets/ExtensionCompatibilityTesterErrors.json';
59 }
60
61 /**
62 * Main entry point for checking extensions to load,
63 * setting up the checks (deleting protocol), and returning
64 * OK if process run through without errors
65 *
66 * @return string "OK" if process ran through without errors
67 */
68 protected function executeAction()
69 {
70 register_shutdown_function(array($this, 'logError'));
71 $getVars = GeneralUtility::_GET('install');
72 if (isset($getVars['extensionCompatibilityTester']) && isset($getVars['extensionCompatibilityTester']['forceCheck']) && ($getVars['extensionCompatibilityTester']['forceCheck'] == 1)) {
73 $this->deleteProtocolFile();
74 }
75 $this->tryToLoadExtLocalconfAndExtTablesOfExtensions($this->getExtensionsToLoad());
76 return 'OK';
77 }
78
79 /**
80 * Delete the protocol files if they exist
81 *
82 * @return void
83 */
84 protected function deleteProtocolFile()
85 {
86 if (file_exists($this->protocolFile)) {
87 unlink($this->protocolFile);
88 }
89 if (file_exists($this->errorProtocolFile)) {
90 unlink($this->errorProtocolFile);
91 }
92 }
93
94 /**
95 * Get extensions that should be loaded.
96 * Fills the TYPO3_LOADED_EXT array.
97 * Only considers local extensions
98 *
99 * @return array
100 */
101 protected function getExtensionsToLoad()
102 {
103 $extensionsToLoad = array();
104 $extensionsToExclude = $this->getExtensionsToExclude();
105 foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $key => $extension) {
106 if (!in_array($key, $extensionsToExclude)) {
107 $extensionsToLoad[$key] = $extension;
108 }
109 }
110 return $extensionsToLoad;
111 }
112
113 /**
114 * Gets extensions already known to be incompatible
115 * This class is recursively called, and this method is needed
116 * to not run into the same errors twice.
117 *
118 * @return array
119 */
120 protected function getExtensionsToExclude()
121 {
122 $exclude = GeneralUtility::getUrl($this->protocolFile);
123 return GeneralUtility::trimExplode(',', (string)$exclude);
124 }
125
126 /**
127 * Tries to load the ext_localconf and ext_tables files of all non-core extensions
128 * Writes current extension name to file and deletes it again when inclusion was
129 * successful.
130 *
131 * @param array $extensions
132 * @return void
133 */
134 protected function tryToLoadExtLocalconfAndExtTablesOfExtensions(array $extensions)
135 {
136 foreach ($extensions as $extensionKey => $extension) {
137 $this->writeCurrentExtensionToFile($extensionKey);
138 $this->loadExtLocalconfForExtension($extensionKey, $extension);
139 $this->removeCurrentExtensionFromFile($extensionKey);
140 }
141 ExtensionManagementUtility::loadBaseTca(false);
142 foreach ($extensions as $extensionKey => $extension) {
143 $this->writeCurrentExtensionToFile($extensionKey);
144 $this->loadExtTablesForExtension($extensionKey, $extension);
145 $this->removeCurrentExtensionFromFile($extensionKey);
146 }
147 }
148
149 /**
150 * Loads ext_tables.php for a single extension. Method is a modified copy of
151 * the original bootstrap method.
152 *
153 * @param string $extensionKey
154 * @param \ArrayAccess $extension
155 * @return void
156 */
157 protected function loadExtTablesForExtension($extensionKey, array $extension)
158 {
159 // In general it is recommended to not rely on it to be globally defined in that
160 // scope, but we can not prohibit this without breaking backwards compatibility
161 global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;
162 global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;
163 global $PAGES_TYPES, $TBE_STYLES;
164 global $_EXTKEY;
165 // Load each ext_tables.php file of loaded extensions
166 $_EXTKEY = $extensionKey;
167 if (isset($extension['ext_tables.php']) && $extension['ext_tables.php']) {
168 // $_EXTKEY and $_EXTCONF are available in ext_tables.php
169 // and are explicitly set in cached file as well
170 $_EXTCONF = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY];
171 require $extension['ext_tables.php'];
172 }
173 }
174
175 /**
176 * Loads ext_localconf.php for a single extension. Method is a modified copy of
177 * the original bootstrap method.
178 *
179 * @param string $extensionKey
180 * @param \ArrayAccess $extension
181 * @return void
182 */
183 protected function loadExtLocalconfForExtension($extensionKey, array $extension)
184 {
185 // This is the main array meant to be manipulated in the ext_localconf.php files
186 // In general it is recommended to not rely on it to be globally defined in that
187 // scope but to use $GLOBALS['TYPO3_CONF_VARS'] instead.
188 // Nevertheless we define it here as global for backwards compatibility.
189 global $TYPO3_CONF_VARS;
190 $_EXTKEY = $extensionKey;
191 if (isset($extension['ext_localconf.php']) && $extension['ext_localconf.php']) {
192 // $_EXTKEY and $_EXTCONF are available in ext_localconf.php
193 // and are explicitly set in cached file as well
194 $_EXTCONF = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY];
195 require $extension['ext_localconf.php'];
196 }
197 }
198
199 /**
200 * Writes $extensionKey to the protocol file by adding it comma separated at
201 * the end of the file.
202 *
203 * @param string $extensionKey
204 * @return void
205 */
206 protected function writeCurrentExtensionToFile($extensionKey)
207 {
208 $incompatibleExtensions = array_filter(GeneralUtility::trimExplode(',', (string)GeneralUtility::getUrl($this->protocolFile)));
209 $incompatibleExtensions = array_merge($incompatibleExtensions, array($extensionKey));
210 GeneralUtility::writeFile($this->protocolFile, implode(', ', $incompatibleExtensions));
211 $this->logError = true;
212 }
213
214 /**
215 * Removes $extensionKey from protocol file.
216 *
217 * @param string $extensionKey
218 * @return void
219 */
220 protected function removeCurrentExtensionFromFile($extensionKey)
221 {
222 $extensionsInFile = array_filter(GeneralUtility::trimExplode(',', (string)GeneralUtility::getUrl($this->protocolFile)));
223 $extensionsByKey = array_flip($extensionsInFile);
224 unset($extensionsByKey[$extensionKey]);
225 $extensionsForFile = array_flip($extensionsByKey);
226 GeneralUtility::writeFile($this->protocolFile, implode(', ', $extensionsForFile));
227 $this->logError = false;
228 }
229
230 /**
231 * Log last occurred error for logging.
232 *
233 * @return void
234 */
235 public function logError()
236 {
237 // Logging is disabled.
238 if (!$this->logError) {
239 return;
240 }
241
242 // Fetch existing errors, add last one and write to file again.
243 $lastError = error_get_last();
244 $errors = array();
245
246 if (file_exists($this->errorProtocolFile)) {
247 $errors = json_decode(GeneralUtility::getUrl($this->errorProtocolFile));
248 }
249 switch ($lastError['type']) {
250 case E_ERROR:
251 $lastError['type'] = 'E_ERROR';
252 break;
253 case E_WARNING:
254 $lastError['type'] = 'E_WARNING';
255 break;
256 case E_PARSE:
257 $lastError['type'] = 'E_PARSE';
258 break;
259 case E_NOTICE:
260 $lastError['type'] = 'E_NOTICE';
261 break;
262 case E_NOTICE:
263 $lastError['type'] = 'E_NOTICE';
264 break;
265 }
266 $errors[] = $lastError;
267
268 GeneralUtility::writeFile($this->errorProtocolFile, json_encode($errors));
269 }
270 }