ContentObjects   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 400
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 12
dl 0
loc 400
rs 8.48
c 0
b 0
f 0
ccs 0
cts 185
cp 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
B prepareLoader() 0 84 8
D loadExtensionTables() 0 113 16
B loadExtensionConfiguration() 0 45 7
A isTaggedWithNoHeader() 0 9 1
B checkAndCreateDummyTemplates() 0 28 6
A writeContentTemplateToTarget() 0 8 1
A getClassPropertiesInLowerCaseUnderscored() 0 9 1
A wrapDefaultTcaConfiguration() 0 20 3
B getDefaultTcaFields() 0 26 6

How to fix   Complexity   

Complex Class

Complex classes like ContentObjects often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ContentObjects, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Loading Slots.
5
 */
6
declare(strict_types = 1);
7
8
namespace HDNET\Autoloader\Loader;
9
10
use Doctrine\Common\Annotations\AnnotationReader;
11
use HDNET\Autoloader\Annotation\EnableRichText;
12
use HDNET\Autoloader\Annotation\NoHeader;
13
use HDNET\Autoloader\Annotation\WizardTab;
14
use HDNET\Autoloader\Loader;
15
use HDNET\Autoloader\LoaderInterface;
16
use HDNET\Autoloader\Service\NameMapperService;
17
use HDNET\Autoloader\SmartObjectRegister;
18
use HDNET\Autoloader\Utility\ClassNamingUtility;
19
use HDNET\Autoloader\Utility\FileUtility;
20
use HDNET\Autoloader\Utility\IconUtility;
21
use HDNET\Autoloader\Utility\ModelUtility;
22
use HDNET\Autoloader\Utility\ReflectionUtility;
23
use HDNET\Autoloader\Utility\TranslateUtility;
24
use TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider;
25
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
26
use TYPO3\CMS\Core\Imaging\IconRegistry;
27
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
30
31
/**
32
 * Loading Slots.
33
 */
34
class ContentObjects implements LoaderInterface
35
{
36
    /**
37
     * Prepare the content object loader.
38
     */
39
    public function prepareLoader(Loader $loader, int $type): array
40
    {
41
        $loaderInformation = [];
42
43
        /** @var AnnotationReader $annotationReader */
44
        $annotationReader = GeneralUtility::makeInstance(AnnotationReader::class);
45
46
        $modelPath = ExtensionManagementUtility::extPath($loader->getExtensionKey()) . 'Classes/Domain/Model/Content/';
47
        $models = FileUtility::getBaseFilesInDir($modelPath, 'php');
48
        if (!empty($models)) {
49
            TranslateUtility::assureLabel(
50
                'tt_content.' .
51
                $loader->getExtensionKey() . '.header',
52
                $loader->getExtensionKey(),
53
                $loader->getExtensionKey() . ' (Header)'
54
            );
55
        }
56
        foreach ($models as $model) {
57
            $key = GeneralUtility::camelCaseToLowerCaseUnderscored($model);
58
            $className = ClassNamingUtility::getFqnByPath(
59
                $loader->getVendorName(),
60
                $loader->getExtensionKey(),
61
                'Domain/Model/Content/' . $model
62
            );
63
            if (!$loader->isInstantiableClass($className)) {
64
                continue;
65
            }
66
            $fieldConfiguration = [];
67
            $richTextFields = [];
68
            $noHeader = $this->isTaggedWithNoHeader($className);
69
70
            $reflectionClass = new \ReflectionClass($className);
71
72
            // create labels in the ext_tables run, to have a valid DatabaseConnection
73
            if (LoaderInterface::EXT_TABLES === $type) {
74
                TranslateUtility::assureLabel('wizard.' . $key, $loader->getExtensionKey(), $key . ' (Title)', null, 'tt_content');
75
                TranslateUtility::assureLabel(
76
                    'wizard.' . $key . '.description',
77
                    $loader->getExtensionKey(),
78
                    $key . ' (Description)',
79
                    null,
80
                    'tt_content'
81
                );
82
                $fieldConfiguration = $this->getClassPropertiesInLowerCaseUnderscored($className);
83
                $defaultFields = $this->getDefaultTcaFields($noHeader, null);
84
                $fieldConfiguration = array_diff($fieldConfiguration, $defaultFields);
85
86
                // RTE manipulation
87
                foreach ($reflectionClass->getProperties() as $property) {
88
                    $richTextField = $annotationReader->getPropertyAnnotation($property, EnableRichText::class);
89
                    if (null === $richTextField) {
90
                        continue;
91
                    }
92
93
                    $search = array_search(
94
                        GeneralUtility::camelCaseToLowerCaseUnderscored($property->getName()),
95
                        $fieldConfiguration,
96
                        true
97
                    );
98
                    if (false !== $search) {
99
                        $richTextFields[] = $fieldConfiguration[$search];
100
                    }
101
                }
102
            }
103
104
            $entry = [
105
                'fieldConfiguration' => implode(',', $fieldConfiguration),
106
                'richTextFields' => $richTextFields,
107
                'modelClass' => $className,
108
                'model' => $model,
109
                'icon' => IconUtility::getByModelName($className, false),
110
                'iconExt' => IconUtility::getByModelName($className, true),
111
                'noHeader' => $noHeader,
112
                'tabInformation' => (string)$annotationReader->getClassAnnotation($reflectionClass, WizardTab::class),
113
            ];
114
115
            SmartObjectRegister::register($entry['modelClass']);
116
            $loaderInformation[$key] = $entry;
117
        }
118
119
        $this->checkAndCreateDummyTemplates($loaderInformation, $loader);
120
121
        return $loaderInformation;
122
    }
123
124
    /**
125
     * Run the loading process for the ext_tables.php file.
126
     */
127
    public function loadExtensionTables(Loader $loader, array $loaderInformation): void
128
    {
129
        if (empty($loaderInformation)) {
130
            return;
131
        }
132
        $createWizardHeader = [];
133
        $predefinedWizards = [
134
            'common',
135
            'special',
136
            'forms',
137
            'plugins',
138
        ];
139
140
        // Add the divider
141
        $GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'][] = [
142
            TranslateUtility::getLllString(
143
                'tt_content.' . $loader->getExtensionKey() . '.header',
144
                $loader->getExtensionKey(),
145
                null,
146
                'tt_content'
147
            ),
148
            '--div--',
149
        ];
150
151
        foreach ($loaderInformation as $e => $config) {
152
            SmartObjectRegister::register($config['modelClass']);
153
            $typeKey = $loader->getExtensionKey() . '_' . $e;
154
155
            ExtensionManagementUtility::addPlugin([
156
                TranslateUtility::getLllOrHelpMessage(
157
                    'content.element.' . $e,
158
                    $loader->getExtensionKey(),
159
                    'tt_content'
160
                ),
161
                $typeKey,
162
                $config['iconExt'],
163
            ], 'CType', $loader->getExtensionKey());
164
165
            if (!isset($GLOBALS['TCA']['tt_content']['types'][$typeKey]['showitem']) || empty($GLOBALS['TCA']['tt_content']['types'][$typeKey]['showitem'])) {
166
                $baseTcaConfiguration = $this->wrapDefaultTcaConfiguration(
167
                    $config['fieldConfiguration'],
168
                    (bool)$config['noHeader']
169
                );
170
171
                if (ExtensionManagementUtility::isLoaded('gridelements')) {
172
                    $baseTcaConfiguration .= ',tx_gridelements_container,tx_gridelements_columns';
173
                }
174
175
                $GLOBALS['TCA']['tt_content']['types'][$typeKey]['showitem'] = $baseTcaConfiguration;
176
            }
177
178
            // RTE
179
            if (isset($config['richTextFields']) && \is_array($config['richTextFields']) && $config['richTextFields']) {
180
                foreach ($config['richTextFields'] as $field) {
181
                    $conf = [
182
                        'config' => [
183
                            'type' => 'text',
184
                            'enableRichtext' => '1',
185
                            'richtextConfiguration' => 'default',
186
                        ],
187
                    ];
188
                    $GLOBALS['TCA']['tt_content']['types'][$typeKey]['columnsOverrides'][$field] = $conf;
189
                }
190
            }
191
192
            IconUtility::addTcaTypeIcon('tt_content', $typeKey, $config['icon']);
193
194
            $tabName = $config['tabInformation'] ?: $loader->getExtensionKey();
195
            if (!\in_array($tabName, $predefinedWizards, true) && !\in_array($tabName, $createWizardHeader, true)) {
196
                $createWizardHeader[] = $tabName;
197
            }
198
199
            /** @var IconRegistry $iconRegistry */
200
            $provider = BitmapIconProvider::class;
201
            if ('svg' === mb_substr(mb_strtolower($config['iconExt']), -3)) {
202
                $provider = SvgIconProvider::class;
203
            }
204
            $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
205
            $iconRegistry->registerIcon($tabName . '-' . $typeKey, $provider, ['source' => $config['iconExt']]);
206
207
            ExtensionManagementUtility::addPageTSConfig('
208
mod.wizards.newContentElement.wizardItems.' . $tabName . '.elements.' . $typeKey . ' {
209
    icon = ' . $config['icon'] . '
210
    iconIdentifier = ' . $tabName . '-' . $typeKey . '
211
    title = ' . TranslateUtility::getLllOrHelpMessage('wizard.' . $e, $loader->getExtensionKey()) . '
212
    description = ' . TranslateUtility::getLllOrHelpMessage(
213
                'wizard.' . $e . '.description',
214
                $loader->getExtensionKey()
215
            ) . '
216
    tt_content_defValues {
217
        CType = ' . $typeKey . '
218
    }
219
}
220
mod.wizards.newContentElement.wizardItems.' . $tabName . '.show := addToList(' . $typeKey . ')');
221
            $cObjectConfiguration = [
222
                'extensionKey' => $loader->getExtensionKey(),
223
                'backendTemplatePath' => 'EXT:' . $loader->getExtensionKey() . '/Resources/Private/Templates/Content/' . $config['model'] . 'Backend.html',
224
                'modelClass' => $config['modelClass'],
225
            ];
226
227
            $GLOBALS['TYPO3_CONF_VARS']['AUTOLOADER']['ContentObject'][$loader->getExtensionKey() . '_' . GeneralUtility::camelCaseToLowerCaseUnderscored($config['model'])] = $cObjectConfiguration;
228
        }
229
230
        if ($createWizardHeader) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $createWizardHeader of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
231
            foreach ($createWizardHeader as $element) {
232
                ExtensionManagementUtility::addPageTSConfig('
233
mod.wizards.newContentElement.wizardItems.' . $element . ' {
234
    show = *
235
    header = ' . TranslateUtility::getLllOrHelpMessage('wizard.' . $element . '.header', $loader->getExtensionKey()) . '
236
}');
237
            }
238
        }
239
    }
240
241
    /**
242
     * Run the loading process for the ext_localconf.php file.
243
     */
244
    public function loadExtensionConfiguration(Loader $loader, array $loaderInformation): void
245
    {
246
        if (empty($loaderInformation)) {
247
            return;
248
        }
249
        static $loadPlugin = true;
250
        $csc = ExtensionManagementUtility::isLoaded('css_styled_content');
251
        $typoScript = '';
252
253
        if ($loadPlugin) {
254
            $loadPlugin = false;
255
            ExtensionUtility::configurePlugin('HDNET.autoloader', 'Content', ['Content' => 'index'], ['Content' => '']);
256
            if (!$csc) {
257
                $typoScript .= 'tt_content = CASE
258
tt_content.key.field = CType';
259
            }
260
        }
261
        foreach ($loaderInformation as $e => $config) {
262
            $typoScript .= '
263
        tt_content.' . $loader->getExtensionKey() . '_' . $e . ' = COA
264
        tt_content.' . $loader->getExtensionKey() . '_' . $e . ' {
265
            ' . ($config['noHeader'] ? '' : '10 =< lib.stdheader') . '
266
            20 = USER
267
            20 {
268
                userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
269
                extensionName = Autoloader
270
                pluginName = Content
271
                vendorName = HDNET
272
                settings {
273
                    contentElement = ' . $config['model'] . '
274
                    extensionKey = ' . $loader->getExtensionKey() . '
275
                    vendorName = ' . $loader->getVendorName() . '
276
                }
277
            }
278
        }
279
        config.tx_extbase.persistence.classes.' . $config['modelClass'] . '.mapping.tableName = tt_content
280
        ';
281
        }
282
283
        if ($csc) {
284
            ExtensionManagementUtility::addTypoScript($loader->getExtensionKey(), 'setup', $typoScript, 43);
285
        } else {
286
            ExtensionManagementUtility::addTypoScriptSetup($typoScript);
287
        }
288
    }
289
290
    /**
291
     * Check if the class is tagged with noHeader.
292
     *
293
     * @param $class
294
     */
295
    protected function isTaggedWithNoHeader($class): bool
296
    {
297
        /** @var AnnotationReader $annotationReader */
298
        $annotationReader = GeneralUtility::makeInstance(AnnotationReader::class);
299
300
        $classNameRef = new \ReflectionClass($class);
301
302
        return null !== $annotationReader->getClassAnnotation($classNameRef, NoHeader::class);
303
    }
304
305
    /**
306
     * Check if the templates are exist and create a dummy, if there
307
     * is no valid template.
308
     */
309
    protected function checkAndCreateDummyTemplates(array $loaderInformation, Loader $loader): void
310
    {
311
        if (empty($loaderInformation)) {
312
            return;
313
        }
314
315
        $siteRelPathPrivate = 'EXT:' . $loader->getExtensionKey() . '/Resources/Private/';
316
        $frontendLayout = GeneralUtility::getFileAbsFileName($siteRelPathPrivate . 'Layouts/Content.html');
317
        if (!is_file($frontendLayout)) {
318
            $this->writeContentTemplateToTarget('FrontendLayout', $frontendLayout);
319
        }
320
        $backendLayout = GeneralUtility::getFileAbsFileName($siteRelPathPrivate . 'Layouts/ContentBackend.html');
321
        if (!is_file($backendLayout)) {
322
            $this->writeContentTemplateToTarget('BackendLayout', $backendLayout);
323
        }
324
325
        foreach ($loaderInformation as $configuration) {
326
            $templatePath = $siteRelPathPrivate . 'Templates/Content/' . $configuration['model'] . '.html';
327
            $absoluteTemplatePath = GeneralUtility::getFileAbsFileName($templatePath);
328
            if (!is_file($absoluteTemplatePath)) {
329
                $beTemplatePath = $siteRelPathPrivate . 'Templates/Content/' . $configuration['model'] . 'Backend.html';
330
                $absoluteBeTemplatePath = GeneralUtility::getFileAbsFileName($beTemplatePath);
331
332
                $this->writeContentTemplateToTarget('Frontend', $absoluteTemplatePath);
333
                $this->writeContentTemplateToTarget('Backend', $absoluteBeTemplatePath);
334
            }
335
        }
336
    }
337
338
    /**
339
     * Write the given content object template to the target path.
340
     *
341
     * @param string $name
342
     * @param string $absoluteTargetPath
343
     */
344
    protected function writeContentTemplateToTarget($name, $absoluteTargetPath): void
345
    {
346
        $template = GeneralUtility::getUrl(ExtensionManagementUtility::extPath(
347
            'autoloader',
348
            'Resources/Private/Templates/ContentObjects/' . $name . '.html'
349
        ));
350
        FileUtility::writeFileAndCreateFolder($absoluteTargetPath, $template);
351
    }
352
353
    /**
354
     * Same as getClassProperties, but the fields are in LowerCaseUnderscored.
355
     *
356
     * @param $className
357
     *
358
     * @return array
359
     */
360
    protected function getClassPropertiesInLowerCaseUnderscored($className)
361
    {
362
        $nameMapperService = GeneralUtility::makeInstance(NameMapperService::class);
363
        $tableName = ModelUtility::getTableName($className);
364
365
        return array_map(function ($value) use ($nameMapperService, $tableName) {
366
            return $nameMapperService->getDatabaseFieldName($tableName, $value);
367
        }, ReflectionUtility::getDeclaringProperties($className));
368
    }
369
370
    /**
371
     * Wrap the given field configuration in the CE default TCA fields.
372
     *
373
     * @param string $configuration
374
     * @param bool   $noHeader
375
     *
376
     * @return string
377
     */
378
    protected function wrapDefaultTcaConfiguration($configuration, $noHeader = false)
379
    {
380
        $languagePrefix = 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf';
381
        $languagePrefixCore = 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf';
382
        $configuration = trim($configuration) ? trim($configuration) . ',' : '';
383
384
        return '--palette--;' . $languagePrefix . ':palette.general;general,
385
    ' . ($noHeader ? '' : '--palette--;' . $languagePrefix . ':palette.header;header,') . '
386
    --div--;LLL:EXT:autoloader/Resources/Private/Language/locallang.xlf:contentData,
387
    ' . $configuration . '
388
    --div--;' . $languagePrefix . ':tabs.appearance,
389
    --palette--;;frames,
390
    --palette--;;appearanceLinks,
391
    --div--;' . $languagePrefixCore . ':language,
392
    --palette--;;language,
393
    --div--;' . $languagePrefixCore . ':access,
394
    --palette--;;hidden,
395
    --palette--;' . $languagePrefix . ':palette.access;access,
396
    --div--;' . $languagePrefixCore . ':extended';
397
    }
398
399
    /**
400
     * Get the fields that are in the default configuration.
401
     *
402
     * @param bool        $noHeader
403
     * @param string|null $configuration
404
     *
405
     * @return array
406
     */
407
    protected function getDefaultTcaFields($noHeader, $configuration = null)
408
    {
409
        if (null === $configuration) {
410
            $configuration = $this->wrapDefaultTcaConfiguration('', $noHeader);
411
        }
412
        $defaultFields = [];
413
        // Note: TCA could be missing in install tool checks, so cast the TCA to array
414
        $existingFields = array_keys((array)$GLOBALS['TCA']['tt_content']['columns']);
415
        $parts = GeneralUtility::trimExplode(',', $configuration, true);
416
        foreach ($parts as $fieldConfiguration) {
417
            $fieldConfiguration = GeneralUtility::trimExplode(';', $fieldConfiguration, true);
418
            if (\in_array($fieldConfiguration[0], $existingFields, true)) {
419
                $defaultFields[] = $fieldConfiguration[0];
420
            } elseif ('--palette--' === $fieldConfiguration[0]) {
421
                $paletteConfiguration = $GLOBALS['TCA']['tt_content']['palettes'][$fieldConfiguration[2]]['showitem'];
422
                if (\is_string($paletteConfiguration)) {
423
                    $defaultFields = array_merge(
424
                        $defaultFields,
425
                        $this->getDefaultTcaFields($noHeader, $paletteConfiguration)
426
                    );
427
                }
428
            }
429
        }
430
431
        return $defaultFields;
432
    }
433
}
434