Passed
Pull Request — release-11.5.x (#3190)
by Rafael
38:55
created

AbstractIndexer::addVirtualContentFieldToRecord()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
namespace ApacheSolrForTypo3\Solr\IndexQueue;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2012-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\ContentObject\Classification;
28
use ApacheSolrForTypo3\Solr\ContentObject\Multivalue;
29
use ApacheSolrForTypo3\Solr\ContentObject\Relation;
30
use ApacheSolrForTypo3\Solr\System\Solr\Document\Document;
31
use TYPO3\CMS\Core\Core\Environment;
32
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
35
use UnexpectedValueException;
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 string $type = '';
52
53
    /**
54
     * Holds field names that are denied to overwrite in thy indexing configuration.
55
     *
56
     * @var array
57
     */
58
    protected static array $unAllowedOverrideFields = ['type'];
59
60
    /**
61
     * @param string $solrFieldName
62
     * @return bool
63
     */
64 61
    public static function isAllowedToOverrideField(string $solrFieldName): bool
65
    {
66 61
        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, TypoScriptFrontendController $tsfe): Document
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, $tsfe);
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
    /**
112
     * Add's the content of the field 'content' from the solr document as virtual field __solr_content in the record,
113
     * to have it available in typoscript.
114
     *
115
     * @param Document $document
116
     * @param array $data
117
     * @return array
118
     */
119 28
    public static function addVirtualContentFieldToRecord(Document $document, array $data): array
120
    {
121 28
        if (isset($document['content'])) {
122 8
            $data['__solr_content'] = $document['content'];
123 8
            return $data;
124
        }
125 20
        return $data;
126
    }
127
128
    /**
129
     * Resolves a field to its value depending on its configuration.
130
     *
131
     * This enables you to configure the indexer to put the item/record through
132
     * cObj processing if wanted/needed. Otherwise the plain item/record value
133
     * is taken.
134
     *
135
     * @param array $indexingConfiguration Indexing configuration as defined in plugin.tx_solr_index.queue.[indexingConfigurationName].fields
136
     * @param string $solrFieldName A Solr field name that is configured in the indexing configuration
137
     * @param array $data A record or item's data
138
     * @param TypoScriptFrontendController $tsfe
139
     * @return string The resolved string value to be indexed
140
     */
141 20
    protected function resolveFieldValue(
142
        array  $indexingConfiguration,
143
        string $solrFieldName,
144
        array  $data,
145
        TypoScriptFrontendController $tsfe
146
    ) {
147 20
        if (isset($indexingConfiguration[$solrFieldName . '.'])) {
148
            // configuration found => need to resolve a cObj
149
150
            // need to change directory to make IMAGE content objects work in BE context
151
            // see http://blog.netzelf.de/lang/de/tipps-und-tricks/tslib_cobj-image-im-backend
152 16
            $backupWorkingDirectory = getcwd();
153 16
            chdir(Environment::getPublicPath() . '/');
154
155 16
            $tsfe->cObj->start($data, $this->type);
156 16
            $fieldValue = $tsfe->cObj->cObjGetSingle(
157 16
                $indexingConfiguration[$solrFieldName],
158 16
                $indexingConfiguration[$solrFieldName . '.']
159
            );
160
161 16
            chdir($backupWorkingDirectory);
162
163 16
            if ($this->isSerializedValue($indexingConfiguration,
164
                $solrFieldName)
165
            ) {
166 16
                $fieldValue = unserialize($fieldValue);
167
            }
168
        } elseif (
169 19
            substr($indexingConfiguration[$solrFieldName], 0, 1) === '<'
170
        ) {
171
            $referencedTsPath = trim(substr($indexingConfiguration[$solrFieldName],
172
                1));
173
            $typoScriptParser = GeneralUtility::makeInstance(TypoScriptParser::class);
174
            // $name and $conf is loaded with the referenced values.
175
            list($name, $conf) = $typoScriptParser->getVal($referencedTsPath, $GLOBALS['TSFE']->tmpl->setup);
176
177
            // need to change directory to make IMAGE content objects work in BE context
178
            // see http://blog.netzelf.de/lang/de/tipps-und-tricks/tslib_cobj-image-im-backend
179
            $backupWorkingDirectory = getcwd();
180
            chdir(Environment::getPublicPath() . '/');
181
182
            $tsfe->start($data, $this->type);
0 ignored issues
show
Bug introduced by
The method start() does not exist on TYPO3\CMS\Frontend\Contr...criptFrontendController. ( Ignorable by Annotation )

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

182
            $tsfe->/** @scrutinizer ignore-call */ 
183
                   start($data, $this->type);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
183
            $fieldValue = $tsfe->cObjGetSingle($name, $conf);
0 ignored issues
show
Bug introduced by
The method cObjGetSingle() does not exist on TYPO3\CMS\Frontend\Contr...criptFrontendController. ( Ignorable by Annotation )

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

183
            /** @scrutinizer ignore-call */ 
184
            $fieldValue = $tsfe->cObjGetSingle($name, $conf);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
184
185
            chdir($backupWorkingDirectory);
186
187
            if ($this->isSerializedValue($indexingConfiguration,
188
                $solrFieldName)
189
            ) {
190
                $fieldValue = unserialize($fieldValue);
191
            }
192
        } else {
193 19
            $fieldValue = $data[$indexingConfiguration[$solrFieldName]];
194
        }
195
196
        // detect and correct type for dynamic fields
197
198
        // find last underscore, substr from there, cut off last character (S/M)
199 20
        $fieldType = substr($solrFieldName, strrpos($solrFieldName, '_') + 1,
200 20
            -1);
201 20
        if (is_array($fieldValue)) {
202 9
            foreach ($fieldValue as $key => $value) {
203 9
                $fieldValue[$key] = $this->ensureFieldValueType($value,
204
                    $fieldType);
205
            }
206
        } else {
207 20
            $fieldValue = $this->ensureFieldValueType($fieldValue, $fieldType);
208
        }
209
210 20
        return $fieldValue;
211
    }
212
213
    // Utility methods
214
215
    /**
216
     * Uses a field's configuration to detect whether its value returned by a
217
     * content object is expected to be serialized and thus needs to be
218
     * unserialized.
219
     *
220
     * @param array $indexingConfiguration Current item's indexing configuration
221
     * @param string $solrFieldName Current field being indexed
222
     * @return bool TRUE if the value is expected to be serialized, FALSE otherwise
223
     */
224 27
    public static function isSerializedValue(array $indexingConfiguration, string $solrFieldName): bool
225
    {
226 27
        return static::isSerializedResultFromRegisteredHook($indexingConfiguration, $solrFieldName)
227 26
            || static::isSerializedResultFromCustomContentElement($indexingConfiguration, $solrFieldName);
228
    }
229
230
    /**
231
     * Checks if the response comes from a custom content element that returns a serialized value.
232
     *
233
     * @param array $indexingConfiguration
234
     * @param string $solrFieldName
235
     * @return bool
236
     */
237 25
    protected static function isSerializedResultFromCustomContentElement(array $indexingConfiguration, string $solrFieldName): bool
238
    {
239 25
        $isSerialized = false;
240
241
        // SOLR_CLASSIFICATION - always returns serialized array
242 25
        if (($indexingConfiguration[$solrFieldName] ?? null) == Classification::CONTENT_OBJECT_NAME) {
243 1
            $isSerialized = true;
244
        }
245
246
        // SOLR_MULTIVALUE - always returns serialized array
247 25
        if (($indexingConfiguration[$solrFieldName] ?? null) == Multivalue::CONTENT_OBJECT_NAME) {
248 2
            $isSerialized = true;
249
        }
250
251
        // SOLR_RELATION - returns serialized array if multiValue option is set
252 25
        if (($indexingConfiguration[$solrFieldName] ?? null) == Relation::CONTENT_OBJECT_NAME && !empty($indexingConfiguration[$solrFieldName . '.']['multiValue'])) {
253 12
            $isSerialized = true;
254
        }
255
256 25
        return $isSerialized;
257
    }
258
259
    /**
260
     * Checks registered hooks if a SerializedValueDetector detects a serialized response.
261
     *
262
     * @param array $indexingConfiguration
263
     * @param string $solrFieldName
264
     * @return bool
265
     */
266 27
    protected static function isSerializedResultFromRegisteredHook(array $indexingConfiguration, string $solrFieldName): bool
267
    {
268 27
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['detectSerializedValue'] ?? null)) {
269 24
            return false;
270
        }
271
272 3
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['detectSerializedValue'] as $classReference) {
273 2
            $serializedValueDetector = GeneralUtility::makeInstance($classReference);
274 2
            if (!$serializedValueDetector instanceof SerializedValueDetector) {
275 1
                $message = get_class($serializedValueDetector) . ' must implement interface ' . SerializedValueDetector::class;
276 1
                throw new UnexpectedValueException($message, 1404471741);
277
            }
278
279 1
            $isSerialized = (boolean)$serializedValueDetector->isSerializedValue($indexingConfiguration, $solrFieldName);
280 1
            if ($isSerialized) {
281 1
                return true;
282
            }
283
        }
284 1
        return false;
285
    }
286
287
    /**
288
     * Makes sure a field's value matches a (dynamic) field's type.
289
     *
290
     * @param mixed $value Value to be added to a document
291
     * @param string $fieldType The dynamic field's type
292
     * @return mixed Returns the value in the correct format for the field type
293
     */
294 20
    protected function ensureFieldValueType($value, $fieldType)
295
    {
296 20
        switch ($fieldType) {
297 20
            case 'int':
298 20
            case 'tInt':
299
                $value = intval($value);
300
                break;
301
302 20
            case 'float':
303 20
            case 'tFloat':
304
                $value = floatval($value);
305
                break;
306
307
            // long and double do not exist in PHP
308
            // simply make sure it somehow looks like a number
309
            // <insert PHP rant here>
310 20
            case 'long':
311 20
            case 'tLong':
312
                // remove anything that's not a number or negative/minus sign
313
                $value = preg_replace('/[^0-9\\-]/', '', $value);
314
                if (trim($value) === '') {
315
                    $value = 0;
316
                }
317
                break;
318 20
            case 'double':
319 20
            case 'tDouble':
320 20
            case 'tDouble4':
321
                // as long as it's numeric we'll take it, int or float doesn't matter
322
                if (!is_numeric($value)) {
323
                    $value = 0;
324
                }
325
                break;
326
327
            default:
328
                // assume things are correct for non-dynamic fields
329
        }
330
331 20
        return $value;
332
    }
333
}
334