Passed
Pull Request — release-11.2.x (#3604)
by Markus
32:12 queued 27:55
created

AbstractIndexer   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Test Coverage

Coverage 75.22%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 41
eloc 113
c 1
b 0
f 0
dl 0
loc 309
ccs 85
cts 113
cp 0.7522
rs 9.1199

8 Methods

Rating   Name   Duplication   Size   Complexity  
A addVirtualContentFieldToRecord() 0 7 2
A isAllowedToOverrideField() 0 3 1
B addDocumentFieldsFromTyposcript() 0 31 7
A isSerializedResultFromRegisteredHook() 0 16 5
A isSerializedValue() 0 9 2
B resolveFieldValue() 0 88 7
A isSerializedResultFromCustomContentElement() 0 20 5
C ensureFieldValueType() 0 38 12

How to fix   Complexity   

Complex Class

Complex classes like AbstractIndexer 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 AbstractIndexer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\IndexQueue;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2012-2015 Ingo Renner <[email protected]>
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use ApacheSolrForTypo3\Solr\ContentObject\Classification;
29
use ApacheSolrForTypo3\Solr\ContentObject\Multivalue;
30
use ApacheSolrForTypo3\Solr\ContentObject\Relation;
31
use ApacheSolrForTypo3\Solr\System\Solr\Document\Document;
32
use TYPO3\CMS\Core\Core\Environment;
33
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
36
37
/**
38
 * An abstract indexer class to collect a few common methods shared with other
39
 * indexers.
40
 *
41
 * @author Ingo Renner <[email protected]>
42
 */
43
abstract class AbstractIndexer
44
{
45
46
    /**
47
     * Holds the type of the data to be indexed, usually that is the table name.
48
     *
49
     * @var string
50
     */
51
    protected $type = '';
52
53
    /**
54
     * Holds field names that are denied to overwrite in thy indexing configuration.
55
     *
56
     * @var array
57
     */
58
    protected static $unAllowedOverrideFields = ['type'];
59
60
    /**
61
     * @param string $solrFieldName
62
     * @return bool
63
     */
64 31
    public static function isAllowedToOverrideField($solrFieldName)
65
    {
66 31
        return !in_array($solrFieldName, static::$unAllowedOverrideFields);
67
    }
68
69
    /**
70
     * Adds fields to the document as defined in $indexingConfiguration
71
     *
72
     * @param Document $document base document to add fields to
73
     * @param array $indexingConfiguration Indexing configuration / mapping
74
     * @param array $data Record data
75
     * @return Document Modified document with added fields
76
     */
77 20
    protected function addDocumentFieldsFromTyposcript(Document $document, array $indexingConfiguration, array $data)
78
    {
79 20
        $data = static::addVirtualContentFieldToRecord($document, $data);
80
81
        // mapping of record fields => solr document fields, resolving cObj
82 20
        foreach ($indexingConfiguration as $solrFieldName => $recordFieldName) {
83 20
            if (is_array($recordFieldName)) {
84
                // configuration for a content object, skipping
85 16
                continue;
86
            }
87
88 20
            if (!static::isAllowedToOverrideField($solrFieldName)) {
89
                throw new InvalidFieldNameException(
90
                    'Must not overwrite field .' . $solrFieldName,
91
                    1435441863
92
                );
93
            }
94
95 20
            $fieldValue = $this->resolveFieldValue($indexingConfiguration, $solrFieldName, $data);
96
97 20
            if (is_array($fieldValue)) {
98
                // multi value
99 9
                $document->setField($solrFieldName, $fieldValue);
100
            } else {
101 20
                if ($fieldValue !== '' && $fieldValue !== null) {
102 19
                    $document->setField($solrFieldName, $fieldValue);
103
                }
104
            }
105
        }
106
107 20
        return $document;
108
    }
109
110
    /**
111
     * Add's the content of the field 'content' from the solr document as virtual field __solr_content in the record,
112
     * to have it available in typoscript.
113
     *
114
     * @param Document $document
115
     * @param array $data
116
     * @return array
117
     */
118 27
    public static function addVirtualContentFieldToRecord(Document $document, array $data): array
119
    {
120 27
        if (isset($document['content'])) {
121 7
            $data['__solr_content'] = $document['content'];
122 7
            return $data;
123
        }
124 20
        return $data;
125
    }
126
127
    /**
128
     * Resolves a field to its value depending on its configuration.
129
     *
130
     * This enables you to configure the indexer to put the item/record through
131
     * cObj processing if wanted/needed. Otherwise the plain item/record value
132
     * is taken.
133
     *
134
     * @param array $indexingConfiguration Indexing configuration as defined in plugin.tx_solr_index.queue.[indexingConfigurationName].fields
135
     * @param string $solrFieldName A Solr field name that is configured in the indexing configuration
136
     * @param array $data A record or item's data
137
     * @return string The resolved string value to be indexed
138
     */
139 20
    protected function resolveFieldValue(
140
        array $indexingConfiguration,
141
        $solrFieldName,
142
        array $data
143
    ) {
144 20
        $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
145
146 20
        if (isset($indexingConfiguration[$solrFieldName . '.'])) {
147
            // configuration found => need to resolve a cObj
148
149
            // need to change directory to make IMAGE content objects work in BE context
150
            // see http://blog.netzelf.de/lang/de/tipps-und-tricks/tslib_cobj-image-im-backend
151 16
            $backupWorkingDirectory = getcwd();
152 16
            chdir(Environment::getPublicPath() . '/');
153
154 16
            $contentObject->start($data, $this->type);
155 16
            $fieldValue = $contentObject->cObjGetSingle(
156 16
                $indexingConfiguration[$solrFieldName],
157 16
                $indexingConfiguration[$solrFieldName . '.']
158
            );
159
160 16
            chdir($backupWorkingDirectory);
161
162 16
            if ($this->isSerializedValue(
163
                $indexingConfiguration,
164 16
                $solrFieldName
165
            )
166
            ) {
167 16
                $fieldValue = unserialize($fieldValue);
168
            }
169 19
        } elseif (substr(
170 19
            $indexingConfiguration[$solrFieldName],
171 19
            0,
172 19
            1
173 19
        ) === '<'
174
        ) {
175
            $referencedTsPath = trim(substr(
176
                $indexingConfiguration[$solrFieldName],
177
                1
178
            ));
179
            $typoScriptParser = GeneralUtility::makeInstance(TypoScriptParser::class);
180
            // $name and $conf is loaded with the referenced values.
181
            list($name, $conf) = $typoScriptParser->getVal(
182
                $referencedTsPath,
183
                $GLOBALS['TSFE']->tmpl->setup
184
            );
185
186
            // need to change directory to make IMAGE content objects work in BE context
187
            // see http://blog.netzelf.de/lang/de/tipps-und-tricks/tslib_cobj-image-im-backend
188
            $backupWorkingDirectory = getcwd();
189
            chdir(Environment::getPublicPath() . '/');
190
191
            $contentObject->start($data, $this->type);
192
            $fieldValue = $contentObject->cObjGetSingle($name, $conf);
193
194
            chdir($backupWorkingDirectory);
195
196
            if ($this->isSerializedValue(
197
                $indexingConfiguration,
198
                $solrFieldName
199
            )
200
            ) {
201
                $fieldValue = unserialize($fieldValue);
202
            }
203
        } else {
204 19
            $fieldValue = $data[$indexingConfiguration[$solrFieldName]];
205
        }
206
207
        // detect and correct type for dynamic fields
208
209
        // find last underscore, substr from there, cut off last character (S/M)
210 20
        $fieldType = substr(
211
            $solrFieldName,
212 20
            strrpos($solrFieldName, '_') + 1,
213 20
            -1
214
        );
215 20
        if (is_array($fieldValue)) {
216 9
            foreach ($fieldValue as $key => $value) {
217 9
                $fieldValue[$key] = $this->ensureFieldValueType(
218
                    $value,
219 9
                    $fieldType
220
                );
221
            }
222
        } else {
223 20
            $fieldValue = $this->ensureFieldValueType($fieldValue, $fieldType);
224
        }
225
226 20
        return $fieldValue;
227
    }
228
229
    // Utility methods
230
231
    /**
232
     * Uses a field's configuration to detect whether its value returned by a
233
     * content object is expected to be serialized and thus needs to be
234
     * unserialized.
235
     *
236
     * @param array $indexingConfiguration Current item's indexing configuration
237
     * @param string $solrFieldName Current field being indexed
238
     * @return bool TRUE if the value is expected to be serialized, FALSE otherwise
239
     */
240 26
    public static function isSerializedValue(array $indexingConfiguration, $solrFieldName)
241
    {
242 26
        $isSerialized = static::isSerializedResultFromRegisteredHook($indexingConfiguration, $solrFieldName);
243 25
        if ($isSerialized === true) {
244 1
            return $isSerialized;
245
        }
246
247 24
        $isSerialized = static::isSerializedResultFromCustomContentElement($indexingConfiguration, $solrFieldName);
248 24
        return $isSerialized;
249
    }
250
251
    /**
252
     * Checks if the response comes from a custom content element that returns a serialized value.
253
     *
254
     * @param array $indexingConfiguration
255
     * @param string $solrFieldName
256
     * @return bool
257
     */
258 24
    protected static function isSerializedResultFromCustomContentElement(array $indexingConfiguration, $solrFieldName): bool
259
    {
260 24
        $isSerialized = false;
261
262
        // SOLR_CLASSIFICATION - always returns serialized array
263 24
        if ($indexingConfiguration[$solrFieldName] == Classification::CONTENT_OBJECT_NAME) {
264 1
            $isSerialized = true;
265
        }
266
267
        // SOLR_MULTIVALUE - always returns serialized array
268 24
        if ($indexingConfiguration[$solrFieldName] == Multivalue::CONTENT_OBJECT_NAME) {
269 1
            $isSerialized = true;
270
        }
271
272
        // SOLR_RELATION - returns serialized array if multiValue option is set
273 24
        if ($indexingConfiguration[$solrFieldName] == Relation::CONTENT_OBJECT_NAME && !empty($indexingConfiguration[$solrFieldName . '.']['multiValue'])) {
274 12
            $isSerialized = true;
275
        }
276
277 24
        return $isSerialized;
278
    }
279
280
    /**
281
     * Checks registered hooks if a SerializedValueDetector detects a serialized response.
282
     *
283
     * @param array $indexingConfiguration
284
     * @param string $solrFieldName
285
     * @return bool
286
     */
287 26
    protected static function isSerializedResultFromRegisteredHook(array $indexingConfiguration, $solrFieldName)
288
    {
289 26
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['detectSerializedValue'])) {
290 23
            return false;
291
        }
292
293 3
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['detectSerializedValue'] as $classReference) {
294 2
            $serializedValueDetector = GeneralUtility::makeInstance($classReference);
295 2
            if (!$serializedValueDetector instanceof SerializedValueDetector) {
296 1
                $message = get_class($serializedValueDetector) . ' must implement interface ' . SerializedValueDetector::class;
297 1
                throw new \UnexpectedValueException($message, 1404471741);
298
            }
299
300 1
            $isSerialized = (boolean)$serializedValueDetector->isSerializedValue($indexingConfiguration, $solrFieldName);
301 1
            if ($isSerialized) {
302 1
                return true;
303
            }
304
        }
305 1
    }
306
307
    /**
308
     * Makes sure a field's value matches a (dynamic) field's type.
309
     *
310
     * @param mixed $value Value to be added to a document
311
     * @param string $fieldType The dynamic field's type
312
     * @return mixed Returns the value in the correct format for the field type
313
     */
314 20
    protected function ensureFieldValueType($value, $fieldType)
315
    {
316
        switch ($fieldType) {
317 20
            case 'int':
318 20
            case 'tInt':
319
                $value = (int)$value;
320
                break;
321
322 20
            case 'float':
323 20
            case 'tFloat':
324
                $value = (float)$value;
325
                break;
326
327
                // long and double do not exist in PHP
328
                // simply make sure it somehow looks like a number
329
                // <insert PHP rant here>
330 20
            case 'long':
331 20
            case 'tLong':
332
                // remove anything that's not a number or negative/minus sign
333
                $value = preg_replace('/[^0-9\\-]/', '', $value);
334
                if (trim($value) === '') {
335
                    $value = 0;
336
                }
337
                break;
338 20
            case 'double':
339 20
            case 'tDouble':
340 20
            case 'tDouble4':
341
                // as long as it's numeric we'll take it, int or float doesn't matter
342
                if (!is_numeric($value)) {
343
                    $value = 0;
344
                }
345
                break;
346
347
            default:
348
            // assume things are correct for non-dynamic fields
349
            }
350
351 20
        return $value;
352
    }
353
}
354