[TASK] Extbase: Revised and cleaned up autoloader. Autoloader is now registered insid...
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Utility / Extension.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25 /**
26 * Utilities to manage plugins and modules of an extension. Also useful to auto-generate the autoloader registry
27 * file ext_autoload.php.
28 *
29 * @package Extbase
30 * @subpackage Utility
31 * @version $ID:$
32 * @author Dmitry Dulepov <dmitry@typo3.org>
33 * @author Sebastian Kurf\9frst <sebastian@typo3.org>
34 * @author Jochen Rau <jochen.rau@typoplanet.de>
35 */
36 class Tx_Extbase_Utility_Extension {
37
38 /**
39 * Build the autoload registry for a given extension and place it ext_autoload.php.
40 *
41 * @param string $extensionKey Key of the extension
42 * @param string $extensionPath full path of the extension
43 * @return string HTML string which should be outputted
44 */
45 public function createAutoloadRegistryForExtension($extensionKey, $extensionPath) {
46 $classNameToFileMapping = array();
47 $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionKey)));
48 $errors = $this->buildAutoloadRegistryForSinglePath($classNameToFileMapping, $extensionPath . 'Classes/', '.*tslib.*', '$extensionClassesPath . \'|\'');
49 if ($errors) {
50 return $errors;
51 }
52 $globalPrefix = '$extensionClassesPath = t3lib_extMgm::extPath(\'' . $extensionKey . '\') . \'Classes/\';';
53
54 $errors = array();
55 foreach ($classNameToFileMapping as $className => $fileName) {
56 if (!(strpos($className, 'tx_' . strtolower($extensionName)) === 0)) {
57 $errors[] = $className . ' does not start with Tx_' . $extensionName . ' and was not added to the autoloader registry.';
58 unset($classNameToFileMapping[$className]);
59 }
60 }
61 $autoloadFileString = $this->generateAutoloadPHPFileData($classNameToFileMapping, $globalPrefix);
62 if (!@file_put_contents($extensionPath . 'ext_autoload.php', $autoloadFileString)) {
63 $errors[] = '<b>' . $extensionPath . 'ext_autoload.php could not be written!</b>';
64 }
65 $errors[] = 'Wrote the following data: <pre>' . htmlspecialchars($autoloadFileString) . '</pre>';
66 return implode('<br />', $errors);
67 }
68
69 /**
70 * Generate autoload PHP file data. Takes an associative array with class name to file mapping, and outputs it as PHP.
71 * Does NOT escape the values in the associative array. Includes the <?php ... ?> syntax and an optional global prefix.
72 *
73 * @param array $classNameToFileMapping class name to file mapping
74 * @param string $globalPrefix Global prefix which is prepended to all code.
75 * @return string The full PHP string
76 */
77 protected function generateAutoloadPHPFileData($classNameToFileMapping, $globalPrefix = '') {
78 $output = '<?php' . PHP_EOL;
79 $output .= '// DO NOT CHANGE THIS FILE! It is automatically generated by Tx_Extbase_Utility_Extension::createAutoloadRegistryForExtension.' . PHP_EOL;
80 $output .= '// This file was generated on ' . date('Y-m-d H:i') . PHP_EOL;
81 $output .= PHP_EOL;
82 $output .= $globalPrefix . PHP_EOL;
83 $output .= 'return array(' . PHP_EOL;
84 foreach ($classNameToFileMapping as $className => $quotedFileName) {
85 $output .= ' \'' . $className . '\' => ' . $quotedFileName . ',' . PHP_EOL;
86 }
87 $output .= ');' . PHP_EOL;
88 $output .= '?>';
89 return $output;
90 }
91
92 /**
93 * Generate the $classNameToFileMapping for a given filePath.
94 *
95 * @param array $classNameToFileMapping (Reference to array) All values are appended to this array.
96 * @param string $path Path which should be crawled
97 * @param string $excludeRegularExpression Exclude regular expression, to exclude certain files from being processed
98 * @param string $valueWrap Wrap for the file name
99 * @return void
100 */
101 protected function buildAutoloadRegistryForSinglePath(&$classNameToFileMapping, $path, $excludeRegularExpression = '', $valueWrap = '\'|\'') {
102 // if (file_exists($path . 'Classes/')) {
103 // return "<b>This appears to be a new-style extension which has its PHP classes inside the Classes/ subdirectory. It is not needed to generate the autoload registry for these extensions.</b>";
104 // }
105 $extensionFileNames = t3lib_div::removePrefixPathFromList(t3lib_div::getAllFilesAndFoldersInPath(array(), $path, 'php', FALSE, 99, $excludeRegularExpression), $path);
106
107 foreach ($extensionFileNames as $extensionFileName) {
108 $classNamesInFile = $this->extractClassNames($path . $extensionFileName);
109 if (!count($classNamesInFile)) continue;
110 foreach ($classNamesInFile as $className) {
111 $classNameToFileMapping[strtolower($className)] = str_replace('|', $extensionFileName, $valueWrap);
112 }
113 }
114 }
115
116 /**
117 * Extracts class names from the given file.
118 *
119 * @param string $filePath File path (absolute)
120 * @return array Class names
121 */
122 protected function extractClassNames($filePath) {
123 $fileContent = php_strip_whitespace($filePath);
124 $classNames = array();
125 if (function_exists('token_get_all')) {
126 $tokens = token_get_all($fileContent);
127 while(1) {
128 // look for "class" or "interface"
129 $token = $this->findToken($tokens, array(T_ABSTRACT, T_CLASS, T_INTERFACE));
130 // fetch "class" token if "abstract" was found
131 if ($token === 'abstract') {
132 $token = $this->findToken($tokens, array(T_CLASS));
133 }
134 if ($token === false) {
135 // end of file
136 break;
137 }
138 // look for the name (a string) skipping only whitespace and comments
139 $token = $this->findToken($tokens, array(T_STRING), array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT));
140 if ($token === false) {
141 // unexpected end of file or token: remove found names because of parse error
142 t3lib_div::sysLog('Parse error in "' . $file. '".', 'Core', 2);
143 $classNames = array();
144 break;
145 }
146 $token = t3lib_div::strtolower($token);
147 // exclude XLASS classes
148 if (strncmp($token, 'ux_', 3)) {
149 $classNames[] = $token;
150 }
151 }
152 } else {
153 // TODO: parse PHP - skip coments and strings, apply regexp only on the remaining PHP code
154 $matches = array();
155 preg_match_all('/^[ \t]*(?:(?:abstract|final)?[ \t]*(?:class|interface))[ \t\n\r]+([a-zA-Z][a-zA-Z_0-9]*)/mS', $fileContent, $matches);
156 $classNames = array_map('t3lib_div::strtolower', $matches[1]);
157 }
158 return $classNames;
159 }
160
161 /**
162 * Find tokens in the tokenList
163 *
164 * @param array $tokenList list of tokens as returned by token_get_all()
165 * @param array $wantedToken the tokens to be found
166 * @param array $intermediateTokens optional: list of tokens that are allowed to skip when looking for the wanted token
167 * @return mixed
168 */
169 protected function findToken(array &$tokenList, array $wantedTokens, array $intermediateTokens = array()) {
170 $skipAllTokens = count($intermediateTokens) ? false : true;
171
172 $returnValue = false;
173 // Iterate with while since we need the current array position:
174 while (list(,$token) = each($tokenList)) {
175 // parse token (see http://www.php.net/manual/en/function.token-get-all.php for format of token list)
176 if (is_array($token)) {
177 list($id, $text) = $token;
178 } else {
179 $id = $text = $token;
180 }
181 if (in_array($id, $wantedTokens)) {
182 $returnValue = $text;
183 break;
184 }
185 // look for another token
186 if ($skipAllTokens || in_array($id, $intermediateTokens)) {
187 continue;
188 }
189 break;
190 }
191 return $returnValue;
192 }
193
194 }
195 ?>