7ccf1c6628bdd0cc44ace16f3c511a1977c1e373
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Utility / ExtensionUtility.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Utility;
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 /**
18 * Utilities to manage plugins and modules of an extension. Also useful to auto-generate the autoloader registry
19 * file ext_autoload.php.
20 */
21 class ExtensionUtility
22 {
23 const PLUGIN_TYPE_PLUGIN = 'list_type';
24 const PLUGIN_TYPE_CONTENT_ELEMENT = 'CType';
25
26 /**
27 * Add auto-generated TypoScript to configure the Extbase Dispatcher.
28 *
29 * When adding a frontend plugin you will have to add both an entry to the TCA definition
30 * of tt_content table AND to the TypoScript template which must initiate the rendering.
31 * Including the plugin code after "defaultContentRendering" adds the necessary TypoScript
32 * for calling the appropriate controller and action of your plugin.
33 * FOR USE IN ext_localconf.php FILES
34 * Usage: 2
35 *
36 * @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
37 * @param string $pluginName must be a unique id for your plugin in UpperCamelCase (the string length of the extension key added to the length of the plugin name should be less than 32!)
38 * @param array $controllerActions is an array of allowed combinations of controller and action stored in an array (controller name as key and a comma separated list of action names as value, the first controller and its first action is chosen as default)
39 * @param array $nonCacheableControllerActions is an optional array of controller name and action names which should not be cached (array as defined in $controllerActions)
40 * @param string $pluginType either \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_PLUGIN (default) or \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
41 * @throws \InvalidArgumentException
42 */
43 public static function configurePlugin($extensionName, $pluginName, array $controllerActions, array $nonCacheableControllerActions = [], $pluginType = self::PLUGIN_TYPE_PLUGIN)
44 {
45 self::checkPluginNameFormat($pluginName);
46 self::checkExtensionNameFormat($extensionName);
47
48 // Check if vendor name is prepended to extensionName in the format {vendorName}.{extensionName}
49 $vendorName = null;
50 $delimiterPosition = strrpos($extensionName, '.');
51 if ($delimiterPosition !== false) {
52 $vendorName = str_replace('.', '\\', substr($extensionName, 0, $delimiterPosition));
53 $extensionName = substr($extensionName, $delimiterPosition + 1);
54
55 if (!empty($vendorName)) {
56 self::checkVendorNameFormat($vendorName, $extensionName);
57 }
58 }
59 $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
60
61 $pluginSignature = strtolower($extensionName . '_' . $pluginName);
62 if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName] ?? false)) {
63 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName] = [];
64 }
65 foreach ($controllerActions as $controllerName => $actionsList) {
66 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['controllers'][$controllerName] = ['actions' => \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $actionsList)];
67 if (!empty($nonCacheableControllerActions[$controllerName])) {
68 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['controllers'][$controllerName]['nonCacheableActions'] = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $nonCacheableControllerActions[$controllerName]);
69 }
70 }
71
72 switch ($pluginType) {
73 case self::PLUGIN_TYPE_PLUGIN:
74 $pluginContent = trim('
75 tt_content.list.20.' . $pluginSignature . ' = USER
76 tt_content.list.20.' . $pluginSignature . ' {
77 userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
78 extensionName = ' . $extensionName . '
79 pluginName = ' . $pluginName . (null !== $vendorName ? ("\n\t" . 'vendorName = ' . $vendorName) : '') . '
80 }');
81 break;
82 case self::PLUGIN_TYPE_CONTENT_ELEMENT:
83 $pluginContent = trim('
84 tt_content.' . $pluginSignature . ' =< lib.contentElement
85 tt_content.' . $pluginSignature . ' {
86 templateName = Generic
87 20 = USER
88 20 {
89 userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
90 extensionName = ' . $extensionName . '
91 pluginName = ' . $pluginName . (null !== $vendorName ? ("\n\t\t" . 'vendorName = ' . $vendorName) : '') . '
92 }
93 }');
94 break;
95 default:
96 throw new \InvalidArgumentException('The pluginType "' . $pluginType . '" is not suported', 1289858856);
97 }
98 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['pluginType'] = $pluginType;
99 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript($extensionName, 'setup', '
100 # Setting ' . $extensionName . ' plugin TypoScript
101 ' . $pluginContent, 'defaultContentRendering');
102 }
103
104 /**
105 * Register an Extbase PlugIn into backend's list of plugins
106 * FOR USE IN Configuration/TCA/Overrides/tt_content.php
107 *
108 * @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
109 * @param string $pluginName must be a unique id for your plugin in UpperCamelCase (the string length of the extension key added to the length of the plugin name should be less than 32!)
110 * @param string $pluginTitle is a speaking title of the plugin that will be displayed in the drop down menu in the backend
111 * @param string $pluginIcon is an icon identifier or file path prepended with "EXT:", that will be displayed in the drop down menu in the backend (optional)
112 * @throws \InvalidArgumentException
113 */
114 public static function registerPlugin($extensionName, $pluginName, $pluginTitle, $pluginIcon = null)
115 {
116 self::checkPluginNameFormat($pluginName);
117 self::checkExtensionNameFormat($extensionName);
118
119 $delimiterPosition = strrpos($extensionName, '.');
120 if ($delimiterPosition !== false) {
121 $extensionName = substr($extensionName, $delimiterPosition + 1);
122 }
123 $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
124 $pluginSignature = strtolower($extensionName) . '_' . strtolower($pluginName);
125
126 // At this point $extensionName is normalized, no matter which format the method was feeded with.
127 // Calculate the original extensionKey from this again.
128 $extensionKey = \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName);
129
130 // pluginType is usually defined by configurePlugin() in the global array. Use this or fall back to default "list_type".
131 $pluginType = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['pluginType'] ?? 'list_type';
132
133 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin(
134 [$pluginTitle, $pluginSignature, $pluginIcon],
135 $pluginType,
136 $extensionKey
137 );
138 }
139
140 /**
141 * Registers an Extbase module (main or sub) to the backend interface.
142 * FOR USE IN ext_tables.php FILES
143 *
144 * @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
145 * @param string $mainModuleName The main module key. So $main would be an index in the $TBE_MODULES array and $sub could be an element in the lists there. If $subModuleName is not set a blank $extensionName module is created
146 * @param string $subModuleName The submodule key.
147 * @param string $position This can be used to set the position of the $sub module within the list of existing submodules for the main module. $position has this syntax: [cmd]:[submodule-key]. cmd can be "after", "before" or "top" (or blank which is default). If "after"/"before" then submodule will be inserted after/before the existing submodule with [submodule-key] if found. If not found, the bottom of list. If "top" the module is inserted in the top of the submodule list.
148 * @param array $controllerActions is an array of allowed combinations of controller and action stored in an array (controller name as key and a comma separated list of action names as value, the first controller and its first action is chosen as default)
149 * @param array $moduleConfiguration The configuration options of the module (icon, locallang.xlf file)
150 * @throws \InvalidArgumentException
151 */
152 public static function registerModule($extensionName, $mainModuleName = '', $subModuleName = '', $position = '', array $controllerActions = [], array $moduleConfiguration = [])
153 {
154 self::checkExtensionNameFormat($extensionName);
155
156 // Check if vendor name is prepended to extensionName in the format {vendorName}.{extensionName}
157 $vendorName = null;
158 if (false !== $delimiterPosition = strrpos($extensionName, '.')) {
159 $vendorName = str_replace('.', '\\', substr($extensionName, 0, $delimiterPosition));
160 $extensionName = substr($extensionName, $delimiterPosition + 1);
161
162 if (!empty($vendorName)) {
163 self::checkVendorNameFormat($vendorName, $extensionName);
164 }
165 }
166
167 $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
168 $defaultModuleConfiguration = [
169 'access' => 'admin',
170 'icon' => 'EXT:extbase/Resources/Public/Icons/Extension.png',
171 'labels' => ''
172 ];
173 if ($mainModuleName !== '' && !array_key_exists($mainModuleName, $GLOBALS['TBE_MODULES'])) {
174 $mainModuleName = $extensionName . \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($mainModuleName);
175 } else {
176 $mainModuleName = $mainModuleName !== '' ? $mainModuleName : 'web';
177 }
178 // add mandatory parameter to use new pagetree
179 if ($mainModuleName === 'web') {
180 $defaultModuleConfiguration['navigationComponentId'] = 'TYPO3/CMS/Backend/PageTree/PageTreeElement';
181 }
182 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($defaultModuleConfiguration, $moduleConfiguration);
183 $moduleConfiguration = $defaultModuleConfiguration;
184 $moduleSignature = $mainModuleName;
185 if ($subModuleName !== '') {
186 $subModuleName = $extensionName . \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($subModuleName);
187 $moduleSignature .= '_' . $subModuleName;
188 }
189 $moduleConfiguration['name'] = $moduleSignature;
190 if (null !== $vendorName) {
191 $moduleConfiguration['vendorName'] = $vendorName;
192 }
193 $moduleConfiguration['extensionName'] = $extensionName;
194 $moduleConfiguration['routeTarget'] = \TYPO3\CMS\Extbase\Core\Bootstrap::class . '::handleBackendRequest';
195 if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature] ?? false)) {
196 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature] = [];
197 }
198 foreach ($controllerActions as $controllerName => $actions) {
199 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature]['controllers'][$controllerName] = [
200 'actions' => \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $actions)
201 ];
202 }
203 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule($mainModuleName, $subModuleName, $position, null, $moduleConfiguration);
204 }
205
206 /**
207 * Register a type converter by class name.
208 *
209 * @param string $typeConverterClassName
210 */
211 public static function registerTypeConverter($typeConverterClassName)
212 {
213 if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters']) ||
214 !is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'])
215 ) {
216 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'] = [];
217 }
218 if (!in_array($typeConverterClassName, $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'])) {
219 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = $typeConverterClassName;
220 }
221 }
222
223 /**
224 * Check a given vendor name for CGL compliance.
225 * Log a deprecation message if it is not.
226 *
227 * @param string $vendorName The vendor name to check
228 * @param string $extensionName The extension name that is affected
229 */
230 protected static function checkVendorNameFormat($vendorName, $extensionName)
231 {
232 if (preg_match('/^[A-Z]/', $vendorName) !== 1) {
233 trigger_error('The vendor name from tx_' . $extensionName . ' must begin with a capital letter.', E_USER_DEPRECATED);
234 }
235 }
236
237 /**
238 * Check a given extension name for validity.
239 *
240 * @param string $extensionName The name of the extension
241 * @throws \InvalidArgumentException
242 */
243 protected static function checkExtensionNameFormat($extensionName)
244 {
245 if (empty($extensionName)) {
246 throw new \InvalidArgumentException('The extension name must not be empty', 1239891990);
247 }
248 }
249
250 /**
251 * Check a given plugin name for validity.
252 *
253 * @param string $pluginName The name of the plugin
254 * @throws \InvalidArgumentException
255 */
256 protected static function checkPluginNameFormat($pluginName)
257 {
258 if (empty($pluginName)) {
259 throw new \InvalidArgumentException('The plugin name must not be empty', 1239891988);
260 }
261 }
262 }