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