Completed
Push — master ( b6358f...8c41f2 )
by Rafael
07:13
created

isSerializedResultFromCustomContentElement()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 7.1941

Importance

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