ConfigurationService   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 392
Duplicated Lines 0 %

Test Coverage

Coverage 68.68%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 178
dl 0
loc 392
ccs 125
cts 182
cp 0.6868
rs 3.12
c 1
b 0
f 0
wmc 66

15 Methods

Rating   Name   Duplication   Size   Complexity  
A addValuesInRange() 0 11 3
B expandExcludeString() 0 35 7
A expandPidList() 0 12 3
A __construct() 0 6 1
A getPidArray() 0 11 2
A removeDisallowedConfigurations() 0 11 4
B getConfigurationFromPageTS() 0 35 7
B getConfigurationFromDatabase() 0 46 9
A getUrlService() 0 4 1
A runExpandParametersHook() 0 15 3
A swapIfFirstIsLargerThanSecond() 0 10 2
A isWrappedInSquareBrackets() 0 3 2
D expandParameters() 0 100 19
A getBackendUser() 0 8 2
A getQueryBuilder() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ConfigurationService 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.

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 ConfigurationService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace AOE\Crawler\Service;
6
7
/*
8
 * (c) 2021 AOE GmbH <[email protected]>
9
 *
10
 * This file is part of the TYPO3 Crawler Extension.
11
 *
12
 * It is free software; you can redistribute it and/or modify it under
13
 * the terms of the GNU General Public License, either version 2
14
 * of the License, or any later version.
15
 *
16
 * For the full copyright and license information, please read the
17
 * LICENSE.txt file that was distributed with this source code.
18
 *
19
 * The TYPO3 project - inspiring people to share!
20
 */
21
22
use AOE\Crawler\Configuration\ExtensionConfigurationProvider;
23
use AOE\Crawler\Domain\Repository\ConfigurationRepository;
24
use Doctrine\DBAL\Connection;
25
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
26
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
use TYPO3\CMS\Core\Core\Bootstrap;
28
use TYPO3\CMS\Core\Database\ConnectionPool;
29
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
30
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
31
use TYPO3\CMS\Core\Database\QueryGenerator;
32
use TYPO3\CMS\Core\Type\Bitmask\Permission;
33
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
use TYPO3\CMS\Core\Utility\MathUtility;
36
use TYPO3\CMS\Extbase\Object\ObjectManager;
37
38
/**
39
 * @internal since v9.2.5
40
 */
41
class ConfigurationService
42
{
43
    /**
44
     * @var BackendUserAuthentication|null
45
     */
46
    private $backendUser;
47
48
    /**
49
     * @var UrlService
50
     */
51
    private $urlService;
52
53
    /**
54
     * @var ConfigurationRepository
55
     */
56
    private $configurationRepository;
57
58
    /**
59
     * @var array
60
     */
61
    private $extensionSettings;
62
63 41
    public function __construct()
64
    {
65 41
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
66 41
        $this->urlService = GeneralUtility::makeInstance(UrlService::class);
67 41
        $this->configurationRepository = $objectManager->get(ConfigurationRepository::class);
68 41
        $this->extensionSettings = GeneralUtility::makeInstance(ExtensionConfigurationProvider::class)->getExtensionConfiguration();
69 41
    }
70
71 6
    public static function removeDisallowedConfigurations(array $allowedConfigurations, array $configurations): array
72
    {
73 6
        if (! empty($allowedConfigurations)) {
74
            // 	remove configuration that does not match the current selection
75 1
            foreach ($configurations as $confKey => $confArray) {
76 1
                if (! in_array($confKey, $allowedConfigurations, true)) {
77 1
                    unset($configurations[$confKey]);
78
                }
79
            }
80
        }
81 6
        return $configurations;
82
    }
83
84 10
    public function getConfigurationFromPageTS(array $pageTSConfig, int $pageId, array $res, string $mountPoint = ''): array
85
    {
86 10
        $maxUrlsToCompile = MathUtility::forceIntegerInRange($this->extensionSettings['maxCompileUrls'], 1, 1000000000, 10000);
87 10
        $crawlerCfg = $pageTSConfig['tx_crawler.']['crawlerCfg.']['paramSets.'] ?? [];
88 10
        foreach ($crawlerCfg as $key => $values) {
89 8
            if (! is_array($values)) {
90 8
                continue;
91
            }
92 8
            $key = str_replace('.', '', $key);
93
            // Sub configuration for a single configuration string:
94 8
            $subCfg = (array) $crawlerCfg[$key . '.'];
95 8
            $subCfg['key'] = $key;
96
97 8
            if (strcmp($subCfg['procInstrFilter'] ?? '', '')) {
98 8
                $subCfg['procInstrFilter'] = implode(',', GeneralUtility::trimExplode(',', $subCfg['procInstrFilter']));
99
            }
100 8
            $pidOnlyList = implode(',', GeneralUtility::trimExplode(',', $subCfg['pidsOnly'], true));
101
102
            // process configuration if it is not page-specific or if the specific page is the current page:
103
            // TODO: Check if $pidOnlyList can be kept as Array instead of imploded
104 8
            if (! strcmp((string) $subCfg['pidsOnly'], '') || GeneralUtility::inList($pidOnlyList, strval($pageId))) {
105
106
                // Explode, process etc.:
107 8
                $res[$key] = [];
108 8
                $res[$key]['subCfg'] = $subCfg;
109 8
                $res[$key]['paramParsed'] = GeneralUtility::explodeUrl2Array($crawlerCfg[$key]);
110 8
                $res[$key]['paramExpanded'] = $this->expandParameters($res[$key]['paramParsed'], $pageId);
111 8
                $res[$key]['origin'] = 'pagets';
112
113 8
                $url = '?id=' . $pageId;
114 8
                $url .= is_string($mountPoint) ? '&MP=' . $mountPoint : '';
115 8
                $res[$key]['URLs'] = $this->getUrlService()->compileUrls($res[$key]['paramExpanded'], [$url], $maxUrlsToCompile);
116
            }
117
        }
118 10
        return $res;
119
    }
120
121 7
    public function getConfigurationFromDatabase(int $pageId, array $res): array
122
    {
123 7
        $maxUrlsToCompile = MathUtility::forceIntegerInRange($this->extensionSettings['maxCompileUrls'], 1, 1000000000, 10000);
124
125 7
        $crawlerConfigurations = $this->configurationRepository->getCrawlerConfigurationRecordsFromRootLine($pageId);
126 7
        foreach ($crawlerConfigurations as $configurationRecord) {
127
128
            // check access to the configuration record
129 1
            if (empty($configurationRecord['begroups']) || $this->getBackendUser()->isAdmin() || UserService::hasGroupAccess($this->getBackendUser()->user['usergroup_cached_list'], $configurationRecord['begroups'])) {
130 1
                $pidOnlyList = implode(',', GeneralUtility::trimExplode(',', $configurationRecord['pidsonly'], true));
131
132
                // process configuration if it is not page-specific or if the specific page is the current page:
133
                // TODO: Check if $pidOnlyList can be kept as Array instead of imploded
134 1
                if (! strcmp($configurationRecord['pidsonly'], '') || GeneralUtility::inList($pidOnlyList, strval($pageId))) {
135 1
                    $key = $configurationRecord['name'];
136
137
                    // don't overwrite previously defined paramSets
138 1
                    if (! isset($res[$key])) {
139
140
                        /* @var $TSparserObject TypoScriptParser */
141 1
                        $TSparserObject = GeneralUtility::makeInstance(TypoScriptParser::class);
142 1
                        $TSparserObject->parse($configurationRecord['processing_instruction_parameters_ts']);
143
144
                        $subCfg = [
145 1
                            'procInstrFilter' => $configurationRecord['processing_instruction_filter'],
146 1
                            'procInstrParams.' => $TSparserObject->setup,
147 1
                            'baseUrl' => $configurationRecord['base_url'],
148 1
                            'force_ssl' => (int) $configurationRecord['force_ssl'],
149 1
                            'userGroups' => $configurationRecord['fegroups'],
150 1
                            'exclude' => $configurationRecord['exclude'],
151 1
                            'key' => $key,
152
                        ];
153
154 1
                        if (! in_array($pageId, $this->expandExcludeString($subCfg['exclude']), true)) {
155 1
                            $res[$key] = [];
156 1
                            $res[$key]['subCfg'] = $subCfg;
157 1
                            $res[$key]['paramParsed'] = GeneralUtility::explodeUrl2Array($configurationRecord['configuration']);
158 1
                            $res[$key]['paramExpanded'] = $this->expandParameters($res[$key]['paramParsed'], $pageId);
159 1
                            $res[$key]['URLs'] = $this->getUrlService()->compileUrls($res[$key]['paramExpanded'], ['?id=' . $pageId], $maxUrlsToCompile);
160 1
                            $res[$key]['origin'] = 'tx_crawler_configuration_' . $configurationRecord['uid'];
161
                        }
162
                    }
163
                }
164
            }
165
        }
166 7
        return $res;
167
    }
168
169 3
    public function expandExcludeString(string $excludeString): array
170
    {
171
        // internal static caches;
172 3
        static $expandedExcludeStringCache;
173 3
        static $treeCache = [];
174
175 3
        if (empty($expandedExcludeStringCache[$excludeString])) {
176 3
            $pidList = [];
177
178 3
            if (! empty($excludeString)) {
179
                /** @var PageTreeView $tree */
180 2
                $tree = GeneralUtility::makeInstance(PageTreeView::class);
181 2
                $tree->init('AND ' . $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
182
183 2
                $excludeParts = GeneralUtility::trimExplode(',', $excludeString);
184
185 2
                foreach ($excludeParts as $excludePart) {
186 2
                    [$pid, $depth] = GeneralUtility::trimExplode('+', $excludePart);
187
188
                    // default is "page only" = "depth=0"
189 2
                    if (empty($depth)) {
190 2
                        $depth = (strpos($excludePart, '+') !== false) ? 99 : 0;
191
                    }
192
193 2
                    $pidList[] = (int) $pid;
194 2
                    if ($depth > 0) {
195
                        $pidList = $this->expandPidList($treeCache, $pid, $depth, $tree, $pidList);
196
                    }
197
                }
198
            }
199
200 3
            $expandedExcludeStringCache[$excludeString] = array_unique($pidList);
201
        }
202
203 3
        return $expandedExcludeStringCache[$excludeString];
204
    }
205
206 7
    protected function getUrlService(): UrlService
207
    {
208 7
        $this->urlService = $this->urlService ?? GeneralUtility::makeInstance(UrlService::class);
209 7
        return $this->urlService;
210
    }
211
212
    /**
213
     * Will expand the parameters configuration to individual values. This follows a certain syntax of the value of each parameter.
214
     * Syntax of values:
215
     * - Basically: If the value is wrapped in [...] it will be expanded according to the following syntax, otherwise the value is taken literally
216
     * - Configuration is splitted by "|" and the parts are processed individually and finally added together
217
     * - For each configuration part:
218
     *         - "[int]-[int]" = Integer range, will be expanded to all values in between, values included, starting from low to high (max. 1000). Example "1-34" or "-40--30"
219
     *         - "_TABLE:[TCA table name];[_PID:[optional page id, default is current page]];[_ENABLELANG:1]" = Look up of table records from PID, filtering out deleted records. Example "_TABLE:tt_content; _PID:123"
220
     *        _ENABLELANG:1 picks only original records without their language overlays
221
     *         - Default: Literal value
222
     */
223 9
    private function expandParameters(array $paramArray, int $pid): array
224
    {
225
        // Traverse parameter names:
226 9
        foreach ($paramArray as $parameter => $parameterValue) {
227 9
            $parameterValue = trim($parameterValue);
228
229
            // If value is encapsulated in square brackets it means there are some ranges of values to find, otherwise the value is literal
230 9
            if ($this->isWrappedInSquareBrackets($parameterValue)) {
231
                // So, find the value inside brackets and reset the paramArray value as an array.
232 9
                $parameterValue = substr($parameterValue, 1, -1);
233 9
                $paramArray[$parameter] = [];
234
235
                // Explode parts and traverse them:
236 9
                $parts = explode('|', $parameterValue);
237 9
                foreach ($parts as $part) {
238
239
                    // Look for integer range: (fx. 1-34 or -40--30 // reads minus 40 to minus 30)
240 9
                    if (preg_match('/^(-?[0-9]+)\s*-\s*(-?[0-9]+)$/', trim($part), $reg)) {
241 1
                        $reg = $this->swapIfFirstIsLargerThanSecond($reg);
242 1
                        $paramArray = $this->addValuesInRange($reg, $paramArray, $parameter);
243 8
                    } elseif (strpos(trim($part), '_TABLE:') === 0) {
244
245
                        // Parse parameters:
246 1
                        $subparts = GeneralUtility::trimExplode(';', $part);
247 1
                        $subpartParams = [];
248 1
                        foreach ($subparts as $spV) {
249 1
                            [$pKey, $pVal] = GeneralUtility::trimExplode(':', $spV);
250 1
                            $subpartParams[$pKey] = $pVal;
251
                        }
252
253
                        // Table exists:
254 1
                        if (isset($GLOBALS['TCA'][$subpartParams['_TABLE']])) {
255
                            $lookUpPid = isset($subpartParams['_PID']) ? intval($subpartParams['_PID']) : $pid;
256
                            $recursiveDepth = isset($subpartParams['_RECURSIVE']) ? intval($subpartParams['_RECURSIVE']) : 0;
257
                            $pidField = isset($subpartParams['_PIDFIELD']) ? trim($subpartParams['_PIDFIELD']) : 'pid';
258
                            $where = $subpartParams['_WHERE'] ?? '';
259
                            $addTable = $subpartParams['_ADDTABLE'] ?? '';
260
261
                            $fieldName = $subpartParams['_FIELD'] ?: 'uid';
262
                            if ($fieldName === 'uid' || $GLOBALS['TCA'][$subpartParams['_TABLE']]['columns'][$fieldName]) {
263
                                $queryBuilder = $this->getQueryBuilder($subpartParams['_TABLE']);
264
                                $pidArray = $this->getPidArray($recursiveDepth, $lookUpPid);
265
266
                                $queryBuilder->getRestrictions()
267
                                    ->removeAll()
268
                                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
269
270
                                $queryBuilder
271
                                    ->select($fieldName)
272
                                    ->from($subpartParams['_TABLE'])
273
                                    ->where(
274
                                        $queryBuilder->expr()->in($pidField, $queryBuilder->createNamedParameter($pidArray, Connection::PARAM_INT_ARRAY)),
275
                                        $where
276
                                    );
277
278
                                if (! empty($addTable)) {
279
                                    // TODO: Check if this works as intended!
280
                                    $queryBuilder->add('from', $addTable);
281
                                }
282
                                $transOrigPointerField = $GLOBALS['TCA'][$subpartParams['_TABLE']]['ctrl']['transOrigPointerField'];
283
284
                                if ($subpartParams['_ENABLELANG'] && $transOrigPointerField) {
285
                                    $queryBuilder->andWhere(
286
                                        $queryBuilder->expr()->lte(
287
                                            $transOrigPointerField,
288
                                            0
289
                                        )
290
                                    );
291
                                }
292
293
                                $statement = $queryBuilder->execute();
294
295
                                $rows = [];
296
                                while ($row = $statement->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\ForwardCompatibility\Result::fetch() has been deprecated: Use fetchNumeric(), fetchAssociative() or fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

296
                                while ($row = /** @scrutinizer ignore-deprecated */ $statement->fetch()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
297
                                    $rows[$row[$fieldName]] = $row;
298
                                }
299
300
                                if (is_array($rows)) {
301 1
                                    $paramArray[$parameter] = array_merge($paramArray[$parameter], array_keys($rows));
302
                                }
303
                            }
304
                        }
305
                    } else {
306
                        // Just add value:
307 7
                        $paramArray[$parameter][] = $part;
308
                    }
309
                    // Hook for processing own expandParameters place holder
310 9
                    $paramArray = $this->runExpandParametersHook($paramArray, $parameter, $part, $pid);
311
                }
312
313
                // Make unique set of values and sort array by key:
314 9
                $paramArray[$parameter] = array_unique($paramArray[$parameter]);
315 9
                ksort($paramArray);
316
            } else {
317
                // Set the literal value as only value in array:
318 8
                $paramArray[$parameter] = [$parameterValue];
319
            }
320
        }
321
322 9
        return $paramArray;
323
    }
324
325 9
    private function isWrappedInSquareBrackets(string $string): bool
326
    {
327 9
        return (strpos($string, '[') === 0 && substr($string, -1) === ']');
328
    }
329
330 1
    private function swapIfFirstIsLargerThanSecond(array $reg): array
331
    {
332
        // Swap if first is larger than last:
333 1
        if ($reg[1] > $reg[2]) {
334
            $temp = $reg[2];
335
            $reg[2] = $reg[1];
336
            $reg[1] = $temp;
337
        }
338
339 1
        return $reg;
340
    }
341
342
    /**
343
     * @return BackendUserAuthentication
344
     */
345 2
    private function getBackendUser()
346
    {
347
        // Make sure the _cli_ user is loaded
348 2
        Bootstrap::initializeBackendAuthentication();
349 2
        if ($this->backendUser === null) {
350 2
            $this->backendUser = $GLOBALS['BE_USER'];
351
        }
352 2
        return $this->backendUser;
353
    }
354
355
    /**
356
     * Get querybuilder for given table
357
     *
358
     * @return QueryBuilder
359
     */
360
    private function getQueryBuilder(string $table)
361
    {
362
        return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
363
    }
364
365
    /**
366
     * @param $parameter
367
     * @param $path
368
     */
369 9
    private function runExpandParametersHook(array $paramArray, $parameter, $path, int $pid): array
370
    {
371 9
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['crawler/class.tx_crawler_lib.php']['expandParameters'])) {
372
            $_params = [
373
                'pObj' => &$this,
374
                'paramArray' => &$paramArray,
375
                'currentKey' => $parameter,
376
                'currentValue' => $path,
377
                'pid' => $pid,
378
            ];
379
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['crawler/class.tx_crawler_lib.php']['expandParameters'] as $_funcRef) {
380
                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
381
            }
382
        }
383 9
        return $paramArray;
384
    }
385
386
    private function getPidArray(int $recursiveDepth, int $lookUpPid): array
387
    {
388
        if ($recursiveDepth > 0) {
389
            /** @var QueryGenerator $queryGenerator */
390
            $queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
391
            $pidList = $queryGenerator->getTreeList($lookUpPid, $recursiveDepth, 0, 1);
392
            $pidArray = GeneralUtility::intExplode(',', $pidList);
393
        } else {
394
            $pidArray = [$lookUpPid];
395
        }
396
        return $pidArray;
397
    }
398
399
    /**
400
     * @param $parameter
401
     *
402
     * Traverse range, add values:
403
     * Limit to size of range!
404
     */
405 1
    private function addValuesInRange(array $reg, array $paramArray, $parameter): array
406
    {
407 1
        $runAwayBrake = 1000;
408 1
        for ($a = $reg[1]; $a <= $reg[2]; $a++) {
409 1
            $paramArray[$parameter][] = $a;
410 1
            $runAwayBrake--;
411 1
            if ($runAwayBrake <= 0) {
412
                break;
413
            }
414
        }
415 1
        return $paramArray;
416
    }
417
418
    /**
419
     * @param $depth
420
     */
421
    private function expandPidList(array $treeCache, string $pid, $depth, PageTreeView $tree, array $pidList): array
422
    {
423
        if (empty($treeCache[$pid][$depth])) {
424
            $tree->reset();
425
            $tree->getTree($pid, $depth);
0 ignored issues
show
Bug introduced by
$pid of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Backend\Tree\V...ractTreeView::getTree(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

425
            $tree->getTree(/** @scrutinizer ignore-type */ $pid, $depth);
Loading history...
426
            $treeCache[$pid][$depth] = $tree->tree;
427
        }
428
429
        foreach ($treeCache[$pid][$depth] as $data) {
430
            $pidList[] = (int) $data['row']['uid'];
431
        }
432
        return $pidList;
433
    }
434
}
435