Passed
Push — master ( 9a88de...e5ef09 )
by Timo
21:32
created

AbstractIndexer::isAllowedToOverrideField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 1
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 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
     */
61 66
    public static function isAllowedToOverrideField($solrFieldName)
62
    {
63 66
        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
     */
74 19
    protected function addDocumentFieldsFromTyposcript(
75
        \Apache_Solr_Document $document,
76
        array $indexingConfiguration,
77
        array $data
78
    ) {
79 19
        $data = static::addVirtualContentFieldToRecord($document, $data);
80
81
        // mapping of record fields => solr document fields, resolving cObj
82 19
        foreach ($indexingConfiguration as $solrFieldName => $recordFieldName) {
83 19
            if (is_array($recordFieldName)) {
84
                // configuration for a content object, skipping
85 17
                continue;
86
            }
87
88 19
            if (!static::isAllowedToOverrideField($solrFieldName)) {
89
                throw new InvalidFieldNameException(
90
                    'Must not overwrite field .' . $solrFieldName,
91
                    1435441863
92
                );
93
            }
94
95 19
            $fieldValue = $this->resolveFieldValue($indexingConfiguration, $solrFieldName, $data);
96
97 19
            if (is_array($fieldValue)) {
98
                // multi value
99 9
                foreach ($fieldValue as $multiValue) {
100 9
                    $document->addField($solrFieldName, $multiValue);
101
                }
102
            } else {
103 19
                if ($fieldValue !== '' && $fieldValue !== null) {
104 19
                    $document->setField($solrFieldName, $fieldValue);
105
                }
106
            }
107
        }
108
109 19
        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 59
    public static function addVirtualContentFieldToRecord(\Apache_Solr_Document $document, array $data): array
122
    {
123 59
        $contentField = $document->getField('content');
124 59
        if (isset($contentField['value'])) {
125 40
            $data['__solr_content'] = $contentField['value'];
126 40
            return $data;
127
        }
128 19
        return $data;
129
    }
130
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
     * cObj processing if wanted/needed. Otherwise the plain item/record value
136
     * is taken.
137
     *
138
     * @param array $indexingConfiguration Indexing configuration as defined in plugin.tx_solr_index.queue.[indexingConfigurationName].fields
139
     * @param string $solrFieldName A Solr field name that is configured in the indexing configuration
140
     * @param array $data A record or item's data
141
     * @return string The resolved string value to be indexed
142
     */
143 19
    protected function resolveFieldValue(
144
        array $indexingConfiguration,
145
        $solrFieldName,
146
        array $data
147
    ) {
148 19
        $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
149
150 19
        if (isset($indexingConfiguration[$solrFieldName . '.'])) {
151
            // configuration found => need to resolve a cObj
152
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 17
            $backupWorkingDirectory = getcwd();
156 17
            chdir(PATH_site);
157
158 17
            $contentObject->start($data, $this->type);
159 17
            $fieldValue = $contentObject->cObjGetSingle(
160 17
                $indexingConfiguration[$solrFieldName],
161 17
                $indexingConfiguration[$solrFieldName . '.']
162
            );
163
164 17
            chdir($backupWorkingDirectory);
165
166 17
            if ($this->isSerializedValue($indexingConfiguration,
167 17
                $solrFieldName)
168
            ) {
169 17
                $fieldValue = unserialize($fieldValue);
170
            }
171 19
        } elseif (substr($indexingConfiguration[$solrFieldName], 0,
172 19
                1) === '<'
173
        ) {
174
            $referencedTsPath = trim(substr($indexingConfiguration[$solrFieldName],
175
                1));
176
            $typoScriptParser = GeneralUtility::makeInstance(TypoScriptParser::class);
177
            // $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
            $backupWorkingDirectory = getcwd();
184
            chdir(PATH_site);
185
186
            $contentObject->start($data, $this->type);
187
            $fieldValue = $contentObject->cObjGetSingle($name, $conf);
188
189
            chdir($backupWorkingDirectory);
190
191
            if ($this->isSerializedValue($indexingConfiguration,
192
                $solrFieldName)
193
            ) {
194
                $fieldValue = unserialize($fieldValue);
195
            }
196
        } else {
197 19
            $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 19
        $fieldType = substr($solrFieldName, strrpos($solrFieldName, '_') + 1,
204 19
            -1);
205 19
        if (is_array($fieldValue)) {
206 9
            foreach ($fieldValue as $key => $value) {
207 9
                $fieldValue[$key] = $this->ensureFieldValueType($value,
208 9
                    $fieldType);
209
            }
210
        } else {
211 19
            $fieldValue = $this->ensureFieldValueType($fieldValue, $fieldType);
212
        }
213
214 19
        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 60
    public static function isSerializedValue(array $indexingConfiguration, $solrFieldName)
229
    {
230 60
        $isSerialized = static::isSerializedResultFromRegisteredHook($indexingConfiguration, $solrFieldName);
231 59
        if ($isSerialized === true) {
232 1
            return $isSerialized;
233
        }
234
235 58
        $isSerialized = static::isSerializedResultFromCustomContentElement($indexingConfiguration, $solrFieldName);
236 58
        return $isSerialized;
237
    }
238
239
    /**
240
     * Checks if the response comes from a custom content element that returns a serialized value.
241
     *
242
     * @param array $indexingConfiguration
243
     * @param string $solrFieldName
244
     * @return bool
245
     */
246 58
    protected static function isSerializedResultFromCustomContentElement(array $indexingConfiguration, $solrFieldName): bool
247
    {
248 58
        $isSerialized = false;
249
250
        // SOLR_CLASSIFICATION - always returns serialized array
251 58
        if ($indexingConfiguration[$solrFieldName] == Classification::CONTENT_OBJECT_NAME) {
252 1
            $isSerialized = true;
253
        }
254
255
        // SOLR_MULTIVALUE - always returns serialized array
256 58
        if ($indexingConfiguration[$solrFieldName] == Multivalue::CONTENT_OBJECT_NAME) {
257 2
            $isSerialized = true;
258
        }
259
260
        // SOLR_RELATION - returns serialized array if multiValue option is set
261 58
        if ($indexingConfiguration[$solrFieldName] == Relation::CONTENT_OBJECT_NAME && !empty($indexingConfiguration[$solrFieldName . '.']['multiValue'])) {
262 44
            $isSerialized = true;
263
        }
264
265 58
        return $isSerialized;
266
    }
267
268
    /**
269
     * Checks registered hooks if a SerializedValueDetector detects a serialized response.
270
     *
271
     * @param array $indexingConfiguration
272
     * @param string $solrFieldName
273
     * @return bool
274
     */
275 60
    protected static function isSerializedResultFromRegisteredHook(array $indexingConfiguration, $solrFieldName)
276
    {
277 60
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['detectSerializedValue'])) {
278 57
            return false;
279
        }
280
281 3
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['detectSerializedValue'] as $classReference) {
282 2
            $serializedValueDetector = GeneralUtility::makeInstance($classReference);
283 2
            if (!$serializedValueDetector instanceof SerializedValueDetector) {
284 1
                $message = get_class($serializedValueDetector) . ' must implement interface ' . SerializedValueDetector::class;
285 1
                throw new \UnexpectedValueException($message, 1404471741);
286
            }
287
288 1
            $isSerialized = (boolean)$serializedValueDetector->isSerializedValue($indexingConfiguration, $solrFieldName);
289 1
            if ($isSerialized) {
290 1
                return true;
291
            }
292
        }
293 1
    }
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 19
    protected function ensureFieldValueType($value, $fieldType)
303
    {
304
        switch ($fieldType) {
305 19
            case 'int':
306 19
            case 'tInt':
307
                $value = intval($value);
308
                break;
309
310 19
            case 'float':
311 19
            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 19
            case 'long':
319 19
            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 19
            case 'double':
327 19
            case 'tDouble':
328 19
            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 19
        return $value;
340
    }
341
}
342