ExtensionUtility::registerModule()   C
last analyzed

Complexity

Conditions 11
Paths 216

Size

Total Lines 60
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 41
dl 0
loc 60
rs 6.2833
c 0
b 0
f 0
cc 11
nc 216
nop 6

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
16
namespace TYPO3\CMS\Extbase\Utility;
17
18
use TYPO3\CMS\Core\Utility\ArrayUtility;
19
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Core\Utility\StringUtility;
22
use TYPO3\CMS\Extbase\Core\Bootstrap;
23
use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchControllerException;
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
class ExtensionUtility
30
{
31
    const PLUGIN_TYPE_PLUGIN = 'list_type';
32
    const PLUGIN_TYPE_CONTENT_ELEMENT = 'CType';
33
34
    /**
35
     * Add auto-generated TypoScript to configure the Extbase Dispatcher.
36
     *
37
     * When adding a frontend plugin you will have to add both an entry to the TCA definition
38
     * of tt_content table AND to the TypoScript template which must initiate the rendering.
39
     * Including the plugin code after "defaultContentRendering" adds the necessary TypoScript
40
     * for calling the appropriate controller and action of your plugin.
41
     * FOR USE IN ext_localconf.php FILES
42
     * Usage: 2
43
     *
44
     * @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
45
     * @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!)
46
     * @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)
47
     * @param array $nonCacheableControllerActions is an optional array of controller name and  action names which should not be cached (array as defined in $controllerActions)
48
     * @param string $pluginType either \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_PLUGIN (default) or \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
49
     * @throws \InvalidArgumentException
50
     */
51
    public static function configurePlugin($extensionName, $pluginName, array $controllerActions, array $nonCacheableControllerActions = [], $pluginType = self::PLUGIN_TYPE_PLUGIN)
52
    {
53
        self::checkPluginNameFormat($pluginName);
54
        self::checkExtensionNameFormat($extensionName);
55
56
        // Check if vendor name is prepended to extensionName in the format {vendorName}.{extensionName}
57
        $delimiterPosition = strrpos($extensionName, '.');
58
        if ($delimiterPosition !== false) {
59
            $vendorName = str_replace('.', '\\', substr($extensionName, 0, $delimiterPosition));
60
            trigger_error(
61
                'Calling method ' . __METHOD__ . ' with argument $extensionName ("' . $extensionName . '") containing the vendor name ("' . $vendorName . '") is deprecated and will stop working in TYPO3 11.0.',
62
                E_USER_DEPRECATED
63
            );
64
            $extensionName = substr($extensionName, $delimiterPosition + 1);
65
66
            if (!empty($vendorName)) {
67
                self::checkVendorNameFormat($vendorName, $extensionName);
68
            }
69
        }
70
        $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
71
72
        $pluginSignature = strtolower($extensionName . '_' . $pluginName);
73
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName] ?? false)) {
74
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName] = [];
75
        }
76
        foreach ($controllerActions as $controllerClassName => $actionsList) {
77
            $controllerAlias = self::resolveControllerAliasFromControllerClassName($controllerClassName);
78
            $vendorName = self::resolveVendorFromExtensionAndControllerClassName($extensionName, $controllerClassName);
79
            if (!empty($vendorName)) {
80
                self::checkVendorNameFormat($vendorName, $extensionName);
81
            }
82
83
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['controllers'][$controllerClassName] = [
84
                'className' => $controllerClassName,
85
                'alias' => $controllerAlias,
86
                'actions' => GeneralUtility::trimExplode(',', $actionsList)
87
            ];
88
89
            if (isset($nonCacheableControllerActions[$controllerClassName]) && !empty($nonCacheableControllerActions[$controllerClassName])) {
90
                $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['controllers'][$controllerClassName]['nonCacheableActions'] = GeneralUtility::trimExplode(
91
                    ',',
92
                    $nonCacheableControllerActions[$controllerClassName]
93
                );
94
            }
95
        }
96
97
        switch ($pluginType) {
98
            case self::PLUGIN_TYPE_PLUGIN:
99
                $pluginContent = trim('
100
tt_content.list.20.' . $pluginSignature . ' = USER
101
tt_content.list.20.' . $pluginSignature . ' {
102
	userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
103
	extensionName = ' . $extensionName . '
104
	pluginName = ' . $pluginName . '
105
}');
106
                break;
107
            case self::PLUGIN_TYPE_CONTENT_ELEMENT:
108
                $pluginContent = trim('
109
tt_content.' . $pluginSignature . ' =< lib.contentElement
110
tt_content.' . $pluginSignature . ' {
111
    templateName = Generic
112
    20 = USER
113
    20 {
114
        userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
115
        extensionName = ' . $extensionName . '
116
        pluginName = ' . $pluginName . '
117
    }
118
}');
119
                break;
120
            default:
121
                throw new \InvalidArgumentException('The pluginType "' . $pluginType . '" is not supported', 1289858856);
122
        }
123
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['pluginType'] = $pluginType;
124
        ExtensionManagementUtility::addTypoScript($extensionName, 'setup', '
125
# Setting ' . $extensionName . ' plugin TypoScript
126
' . $pluginContent, 'defaultContentRendering');
127
    }
128
129
    /**
130
     * Register an Extbase PlugIn into backend's list of plugins
131
     * FOR USE IN Configuration/TCA/Overrides/tt_content.php
132
     *
133
     * @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
134
     * @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!)
135
     * @param string $pluginTitle is a speaking title of the plugin that will be displayed in the drop down menu in the backend
136
     * @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)
137
     * @param string $group add this plugin to a plugin group, should be something like "news" or the like, "default" as regular
138
     * @throws \InvalidArgumentException
139
     */
140
    public static function registerPlugin($extensionName, $pluginName, $pluginTitle, $pluginIcon = null, $group = 'default')
141
    {
142
        self::checkPluginNameFormat($pluginName);
143
        self::checkExtensionNameFormat($extensionName);
144
145
        $delimiterPosition = strrpos($extensionName, '.');
146
        if ($delimiterPosition !== false) {
147
            $vendorName = str_replace('.', '\\', substr($extensionName, 0, $delimiterPosition));
148
            trigger_error(
149
                'Calling method ' . __METHOD__ . ' with argument $extensionName ("' . $extensionName . '") containing the vendor name ("' . $vendorName . '") is deprecated and will stop working in TYPO3 11.0.',
150
                E_USER_DEPRECATED
151
            );
152
            $extensionName = substr($extensionName, $delimiterPosition + 1);
153
        }
154
        $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
155
        $pluginSignature = strtolower($extensionName) . '_' . strtolower($pluginName);
156
157
        // At this point $extensionName is normalized, no matter which format the method was fed with.
158
        // Calculate the original extensionKey from this again.
159
        $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName);
160
161
        // pluginType is usually defined by configurePlugin() in the global array. Use this or fall back to default "list_type".
162
        $pluginType = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['pluginType'] ?? 'list_type';
163
164
        $itemArray = [$pluginTitle, $pluginSignature, $pluginIcon];
165
        if ($group) {
166
            $itemArray[3] = $group;
167
        }
168
        ExtensionManagementUtility::addPlugin(
169
            $itemArray,
170
            $pluginType,
171
            $extensionKey
172
        );
173
    }
174
175
    /**
176
     * Registers an Extbase module (main or sub) to the backend interface.
177
     * FOR USE IN ext_tables.php FILES
178
     *
179
     * @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
180
     * @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
181
     * @param string $subModuleName The submodule key.
182
     * @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.
183
     * @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)
184
     * @param array $moduleConfiguration The configuration options of the module (icon, locallang.xlf file)
185
     * @throws \InvalidArgumentException
186
     */
187
    public static function registerModule($extensionName, $mainModuleName = '', $subModuleName = '', $position = '', array $controllerActions = [], array $moduleConfiguration = [])
188
    {
189
        self::checkExtensionNameFormat($extensionName);
190
191
        // Check if vendor name is prepended to extensionName in the format {vendorName}.{extensionName}
192
        if (false !== $delimiterPosition = strrpos($extensionName, '.')) {
193
            trigger_error(
194
                'Calling method ' . __METHOD__ . ' with argument $extensionName containing the vendor name is deprecated and will stop working in TYPO3 11.0.',
195
                E_USER_DEPRECATED
196
            );
197
            $vendorName = str_replace('.', '\\', substr($extensionName, 0, $delimiterPosition));
198
            $extensionName = substr($extensionName, $delimiterPosition + 1);
199
200
            if (!empty($vendorName)) {
201
                self::checkVendorNameFormat($vendorName, $extensionName);
202
            }
203
        }
204
205
        $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
206
        $defaultModuleConfiguration = [
207
            'access' => 'admin',
208
            'icon' => 'EXT:extbase/Resources/Public/Icons/Extension.png',
209
            'labels' => ''
210
        ];
211
        if ($mainModuleName !== '' && !array_key_exists($mainModuleName, $GLOBALS['TBE_MODULES'])) {
212
            $mainModuleName = $extensionName . GeneralUtility::underscoredToUpperCamelCase($mainModuleName);
213
        } else {
214
            $mainModuleName = $mainModuleName !== '' ? $mainModuleName : 'web';
215
        }
216
        // add mandatory parameter to use new pagetree
217
        if ($mainModuleName === 'web') {
218
            $defaultModuleConfiguration['navigationComponentId'] = 'TYPO3/CMS/Backend/PageTree/PageTreeElement';
219
        }
220
        ArrayUtility::mergeRecursiveWithOverrule($defaultModuleConfiguration, $moduleConfiguration);
221
        $moduleConfiguration = $defaultModuleConfiguration;
222
        $moduleSignature = $mainModuleName;
223
        if ($subModuleName !== '') {
224
            $subModuleName = $extensionName . GeneralUtility::underscoredToUpperCamelCase($subModuleName);
225
            $moduleSignature .= '_' . $subModuleName;
226
        }
227
        $moduleConfiguration['name'] = $moduleSignature;
228
        $moduleConfiguration['extensionName'] = $extensionName;
229
        $moduleConfiguration['routeTarget'] = Bootstrap::class . '::handleBackendRequest';
230
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature] ?? false)) {
231
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature] = [];
232
        }
233
        foreach ($controllerActions as $controllerClassName => $actionsList) {
234
            $controllerAlias = self::resolveControllerAliasFromControllerClassName($controllerClassName);
235
            $vendorName = self::resolveVendorFromExtensionAndControllerClassName($extensionName, $controllerClassName);
236
            if (!empty($vendorName)) {
237
                self::checkVendorNameFormat($vendorName, $extensionName);
238
            }
239
240
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature]['controllers'][$controllerClassName] = [
241
                'className' => $controllerClassName,
242
                'alias' => $controllerAlias,
243
                'actions' => GeneralUtility::trimExplode(',', $actionsList)
244
            ];
245
        }
246
        ExtensionManagementUtility::addModule($mainModuleName, $subModuleName, $position, null, $moduleConfiguration);
247
    }
248
249
    /**
250
     * Returns the object name of the controller defined by the extension name and
251
     * controller name
252
     *
253
     * @param string $vendor
254
     * @param string $extensionKey
255
     * @param string $subPackageKey
256
     * @param string $controllerAlias
257
     * @return string The controller's Object Name
258
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchControllerException if the controller does not exist
259
     */
260
    public static function getControllerClassName(
261
        string $vendor,
262
        string $extensionKey,
263
        string $subPackageKey,
264
        string $controllerAlias
265
    ): string {
266
        $objectName = str_replace(
267
            [
268
                '@extension',
269
                '@subpackage',
270
                '@controller',
271
                '@vendor',
272
                '\\\\'
273
            ],
274
            [
275
                $extensionKey,
276
                $subPackageKey,
277
                $controllerAlias,
278
                $vendor,
279
                '\\'
280
            ],
281
            '@vendor\@extension\@subpackage\Controller\@controllerController'
282
        );
283
284
        if ($objectName === false) {
285
            throw new NoSuchControllerException('The controller object "' . $objectName . '" does not exist.', 1220884009);
286
        }
287
        return trim($objectName, '\\');
288
    }
289
290
    /**
291
     * @param string $controllerClassName
292
     * @return string
293
     */
294
    public static function resolveControllerAliasFromControllerClassName(string $controllerClassName): string
295
    {
296
        // This method has been adjusted for TYPO3 10.3 to mitigate the issue that controller aliases
297
        // could not longer be calculated from controller classes when calling
298
        // \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin().
299
        //
300
        // The idea for version 11 is to let the user choose a controller alias and to check for its
301
        // uniqueness per plugin. That way, the core does no longer rely on the namespace of
302
        // controller classes to be in a specific format.
303
        //
304
        // todo: Change the way plugins are registered and enforce a controller alias to be set by
305
        //       the user to also free the core from guessing a simple alias by looking at the
306
        //       class name. This makes it possible to choose controller class names without a
307
        //       controller suffix.
308
309
        $strLen = strlen('Controller');
310
311
        if (!StringUtility::endsWith($controllerClassName, 'Controller')) {
312
            return '';
313
        }
314
315
        $controllerClassNameWithoutControllerSuffix = substr($controllerClassName, 0, -$strLen);
316
317
        if (strrpos($controllerClassNameWithoutControllerSuffix, 'Controller\\') === false) {
318
            $positionOfLastSlash = (int)strrpos($controllerClassNameWithoutControllerSuffix, '\\');
319
            $positionOfLastSlash += $positionOfLastSlash === 0 ? 0 : 1;
320
321
            return substr($controllerClassNameWithoutControllerSuffix, $positionOfLastSlash);
322
        }
323
324
        $positionOfControllerNamespacePart = (int)strrpos(
325
            $controllerClassNameWithoutControllerSuffix,
326
            'Controller\\'
327
        );
328
329
        return substr(
330
            $controllerClassNameWithoutControllerSuffix,
331
            $positionOfControllerNamespacePart + $strLen + 1
332
        );
333
    }
334
335
    /**
336
     * @param string $extensionName
337
     * @param string $controllerClassName
338
     * @return string
339
     */
340
    public static function resolveVendorFromExtensionAndControllerClassName(string $extensionName, string $controllerClassName): string
341
    {
342
        if (strpos($controllerClassName, '\\') === false) {
343
            // Does not work with non namespaced classes
344
            return '';
345
        }
346
347
        if (false === $extensionNamePosition = strpos($controllerClassName, $extensionName)) {
348
            // Does not work for classes that do not include the extension name as namespace part
349
            return '';
350
        }
351
352
        if (--$extensionNamePosition < 0) {
353
            return '';
354
        }
355
356
        return substr(
357
            $controllerClassName,
358
            0,
359
            $extensionNamePosition
360
        );
361
    }
362
363
    /**
364
     * Register a type converter by class name.
365
     *
366
     * @param string $typeConverterClassName
367
     */
368
    public static function registerTypeConverter($typeConverterClassName)
369
    {
370
        if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters']) ||
371
            !is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'])
372
        ) {
373
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'] = [];
374
        }
375
        if (!in_array($typeConverterClassName, $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'])) {
376
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = $typeConverterClassName;
377
        }
378
    }
379
380
    /**
381
     * Check a given vendor name for CGL compliance.
382
     * Log a deprecation message if it is not.
383
     *
384
     * @param string $vendorName The vendor name to check
385
     * @param string $extensionName The extension name that is affected
386
     */
387
    protected static function checkVendorNameFormat($vendorName, $extensionName)
388
    {
389
        if (preg_match('/^[A-Z]/', $vendorName) !== 1) {
390
            trigger_error('The vendor name from tx_' . $extensionName . ' must begin with a capital letter.', E_USER_DEPRECATED);
391
        }
392
    }
393
394
    /**
395
     * Check a given extension name for validity.
396
     *
397
     * @param string $extensionName The name of the extension
398
     * @throws \InvalidArgumentException
399
     */
400
    protected static function checkExtensionNameFormat($extensionName)
401
    {
402
        if (empty($extensionName)) {
403
            throw new \InvalidArgumentException('The extension name must not be empty', 1239891990);
404
        }
405
    }
406
407
    /**
408
     * Check a given plugin name for validity.
409
     *
410
     * @param string $pluginName The name of the plugin
411
     * @throws \InvalidArgumentException
412
     */
413
    protected static function checkPluginNameFormat($pluginName)
414
    {
415
        if (empty($pluginName)) {
416
            throw new \InvalidArgumentException('The plugin name must not be empty', 1239891988);
417
        }
418
    }
419
}
420