Passed
Branch master (cd83de)
by Timo
05:52
created

Relation::getTranslationOverlay()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.7462

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 3
cts 7
cp 0.4286
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2.7462
1
<?php
2
namespace ApacheSolrForTypo3\Solr\ContentObject;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2011-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 TYPO3\CMS\Core\Database\RelationHandler;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
30
use TYPO3\CMS\Frontend\Page\PageRepository;
31
32
/**
33
 * A content object (cObj) to resolve relations between database records
34
 *
35
 * Configuration options:
36
 *
37
 * localField: the record's field to use to resolve relations
38
 * foreignLabelField: Usually the label field to retrieve from the related records is determined automatically using TCA, using this option the desired field can be specified explicitly
39
 * multiValue: whether to return related records suitable for a multi value field
40
 * singleValueGlue: when not using multiValue, the related records need to be concatenated using a glue string, by default this is ", ". Using this option a custom glue can be specified. The custom value must be wrapped by pipe (|) characters.
41
 * relationTableSortingField: field in an mm relation table to sort by, usually "sorting"
42
 * enableRecursiveValueResolution: if the specified remote table's label field is a relation to another table, the value will be resolve by following the relation recursively.
43
 * removeEmptyValues: Removes empty values when resolving relations, defaults to TRUE
44
 * removeDuplicateValues: Removes duplicate values
45
 *
46
 * @author Ingo Renner <[email protected]>
47
 */
48
class Relation
49
{
50
    const CONTENT_OBJECT_NAME = 'SOLR_RELATION';
51
52
    /**
53
     * Content object configuration
54
     *
55
     * @var array
56
     */
57
    protected $configuration = array();
58
59
    /**
60
     * Constructor.
61
     *
62
     */
63 5
    public function __construct()
64
    {
65 5
        $this->configuration['enableRecursiveValueResolution'] = 1;
66 5
        $this->configuration['removeEmptyValues'] = 1;
67 5
    }
68
69
    /**
70
     * Executes the SOLR_RELATION content object.
71
     *
72
     * Resolves relations between records. Currently supported relations are
73
     * TYPO3-style m:n relations.
74
     * May resolve single value and multi value relations.
75
     *
76
     * @param string $name content object name 'SOLR_RELATION'
77
     * @param array $configuration for the content object
78
     * @param string $TyposcriptKey not used
79
     * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentContentObject parent content object
80
     * @return string serialized array representation of the given list
81
     */
82 5
    public function cObjGetSingleExt(
83
        $name,
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
        array $configuration,
85
        $TyposcriptKey,
0 ignored issues
show
Unused Code introduced by
The parameter $TyposcriptKey is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
86
        $parentContentObject
87
    ) {
88 5
        $this->configuration = array_merge($this->configuration, $configuration);
89
90 5
        $relatedItems = $this->getRelatedItems($parentContentObject);
91
92 5
        if (!empty($this->configuration['removeDuplicateValues'])) {
93
            $relatedItems = array_unique($relatedItems);
94
        }
95
96 5
        if (empty($configuration['multiValue'])) {
97
            // single value, need to concatenate related items
98
            $singleValueGlue = ', ';
99
100
            if (!empty($configuration['singleValueGlue'])) {
101
                $singleValueGlue = trim($configuration['singleValueGlue'], '|');
102
            }
103
104
            $result = implode($singleValueGlue, $relatedItems);
105
        } else {
106
            // multi value, need to serialize as content objects must return strings
107 5
            $result = serialize($relatedItems);
108
        }
109
110 5
        return $result;
111
    }
112
113
    /**
114
     * Gets the related items of the current record's configured field.
115
     *
116
     * @param ContentObjectRenderer $parentContentObject parent content object
117
     * @return array Array of related items, values already resolved from related records
118
     */
119 5
    protected function getRelatedItems(
120
        ContentObjectRenderer $parentContentObject
121
    ) {
122 5
        $relatedItems = array();
123
124 5
        list($localTableName, $localRecordUid) = explode(':',
125 5
            $parentContentObject->currentRecord);
126
127 5
        $localTableTca = $GLOBALS['TCA'][$localTableName];
128 5
        $localFieldName = $this->configuration['localField'];
129
130 5
        if (isset($localTableTca['columns'][$localFieldName])) {
131 5
            $localFieldTca = $localTableTca['columns'][$localFieldName];
132 5
            $localRecordUid = $this->getUidOfRecordOverlay($localTableName, $localRecordUid);
133 5
            if (isset($localFieldTca['config']['MM']) && trim($localFieldTca['config']['MM']) !== '') {
134 3
                $relatedItems = $this->getRelatedItemsFromMMTable($localTableName,
135
                    $localRecordUid, $localFieldTca);
136
            } else {
137 2
                $relatedItems = $this->getRelatedItemsFromForeignTable($localTableName,
138
                    $localRecordUid, $localFieldTca, $parentContentObject);
139
            }
140
        }
141
142 5
        return $relatedItems;
143
    }
144
145
    /**
146
     * Gets the related items from a table using a n:m relation.
147
     *
148
     * @param string $localTableName Local table name
149
     * @param int $localRecordUid Local record uid
150
     * @param array $localFieldTca The local table's TCA
151
     * @return array Array of related items, values already resolved from related records
152
     */
153 3
    protected function getRelatedItemsFromMMTable(
154
        $localTableName,
155
        $localRecordUid,
156
        array $localFieldTca
157
    ) {
158 3
        $relatedItems = [];
159 3
        $foreignTableName = $localFieldTca['config']['foreign_table'];
160 3
        $foreignTableTca = $GLOBALS['TCA'][$foreignTableName];
161 3
        $foreignTableLabelField = $this->resolveForeignTableLabelField($foreignTableTca);
162 3
        $mmTableName = $localFieldTca['config']['MM'];
163
164
        // Remove the first option of foreignLabelField for recursion
165 3
        if (strpos($this->configuration['foreignLabelField'], '.') !== false) {
166
            $foreignTableLabelFieldArr = explode('.', $this->configuration['foreignLabelField']);
167
            unset($foreignTableLabelFieldArr[0]);
168
            $this->configuration['foreignLabelField'] = implode('.', $foreignTableLabelFieldArr);
169
        }
170
171 3
        $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
172 3
        $relationHandler->start('', $foreignTableName, $mmTableName, $localRecordUid, $localTableName, $localFieldTca['config']);
173 3
        $selectUids = $relationHandler->tableArray[$foreignTableName];
174 3
        if (!is_array($selectUids) || count($selectUids) <= 0) {
175
            return $relatedItems;
176
        }
177
178 3
        $pageSelector = GeneralUtility::makeInstance(PageRepository::class);
179 3
        $whereClause = $pageSelector->enableFields($foreignTableName);
180 3
        $relatedRecords = $this->getRelatedRecords($foreignTableName, $selectUids, $whereClause);
181 3
        foreach ($relatedRecords as $record) {
182 3
            if (isset($foreignTableTca['columns'][$foreignTableLabelField]['config']['foreign_table'])
183 3
                && $this->configuration['enableRecursiveValueResolution']
184
            ) {
185
                if (strpos($this->configuration['foreignLabelField'], '.') !== false) {
186
                    $foreignLabelFieldArr = explode('.', $this->configuration['foreignLabelField']);
187
                    unset($foreignLabelFieldArr[0]);
188
                    $this->configuration['foreignLabelField'] = implode('.', $foreignLabelFieldArr);
189
                }
190
191
                $this->configuration['localField'] = $foreignTableLabelField;
192
193
                $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
194
                $contentObject->start($record, $foreignTableName);
195
196
                return $this->getRelatedItems($contentObject);
197
            } else {
198 3
                if ($GLOBALS['TSFE']->sys_language_uid > 0) {
199 1
                    $record = $this->getTranslationOverlay($foreignTableName, $record);
200
                }
201 3
                $relatedItems[] = $record[$foreignTableLabelField];
202
            }
203
        }
204
205 3
        return $relatedItems;
206
    }
207
208
    /**
209
     * Resolves the field to use as the related item's label depending on TCA
210
     * and TypoScript configuration
211
     *
212
     * @param array $foreignTableTca The foreign table's TCA
213
     * @return string The field to use for the related item's label
214
     */
215 5
    protected function resolveForeignTableLabelField(array $foreignTableTca)
216
    {
217 5
        $foreignTableLabelField = $foreignTableTca['ctrl']['label'];
218
219
        // when foreignLabelField is not enabled we can return directly
220 5
        if (empty($this->configuration['foreignLabelField'])) {
221 4
            return $foreignTableLabelField;
222
        }
223
224 2
        if (strpos($this->configuration['foreignLabelField'], '.') !== false) {
225 1
            list($foreignTableLabelField) = explode('.', $this->configuration['foreignLabelField'], 2);
226
        } else {
227 2
            $foreignTableLabelField = $this->configuration['foreignLabelField'];
228
        }
229
230 2
        return $foreignTableLabelField;
231
    }
232
233
    /**
234
     * Return the translated record
235
     *
236
     * @param string $tableName
237
     * @param array $record
238
     * @return mixed
239
     */
240 1
    protected function getTranslationOverlay($tableName, $record)
241
    {
242 1
        if ($tableName == 'pages') {
243
            return $GLOBALS['TSFE']->sys_page->getPageOverlay($record, $GLOBALS['TSFE']->sys_language_uid);
244
        }
245
246 1
        return $GLOBALS['TSFE']->sys_page->getRecordOverlay($tableName, $record, $GLOBALS['TSFE']->sys_language_uid);
247
    }
248
249
    /**
250
     * Gets the related items from a table using a 1:n relation.
251
     *
252
     * @param string $localTableName Local table name
253
     * @param int $localRecordUid Local record uid
254
     * @param array $localFieldTca The local table's TCA
255
     * @param ContentObjectRenderer $parentContentObject parent content object
256
     * @return array Array of related items, values already resolved from related records
257
     */
258 2
    protected function getRelatedItemsFromForeignTable(
259
        $localTableName,
260
        $localRecordUid,
261
        array $localFieldTca,
262
        ContentObjectRenderer $parentContentObject
263
    ) {
264 2
        $relatedItems = [];
265 2
        $foreignTableName = $localFieldTca['config']['foreign_table'];
266 2
        $foreignTableTca = $GLOBALS['TCA'][$foreignTableName];
267 2
        $foreignTableLabelField = $this->resolveForeignTableLabelField($foreignTableTca);
268
269
            /** @var $relationHandler RelationHandler */
270 2
        $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
271
272 2
        $itemList = isset($parentContentObject->data[$this->configuration['localField']]) ?
273 2
                        $parentContentObject->data[$this->configuration['localField']] : '';
274
275 2
        $relationHandler->start($itemList, $foreignTableName, '', $localRecordUid, $localTableName, $localFieldTca['config']);
276 2
        $selectUids = $relationHandler->tableArray[$foreignTableName];
277
278 2
        if (!is_array($selectUids) || count($selectUids) <= 0) {
279
            return $relatedItems;
280
        }
281
282 2
        $pageSelector = GeneralUtility::makeInstance(PageRepository::class);
283 2
        $whereClause  = $pageSelector->enableFields($foreignTableName);
284 2
        $relatedRecords = $this->getRelatedRecords($foreignTableName, $selectUids, $whereClause);
285
286 2
        foreach ($relatedRecords as $relatedRecord) {
287 2
            $resolveRelatedValue = $this->resolveRelatedValue(
288
                $relatedRecord,
289
                $foreignTableTca,
290
                $foreignTableLabelField,
291
                $parentContentObject,
292
                $foreignTableName
293
            );
294 2
            if (!empty($resolveRelatedValue) || !$this->configuration['removeEmptyValues']) {
295 2
                $relatedItems[] = $resolveRelatedValue;
296
            }
297
        }
298
299 2
        return $relatedItems;
300
    }
301
302
    /**
303
     * Resolves the value of the related field. If the related field's value is
304
     * a relation itself, this method takes care of resolving it recursively.
305
     *
306
     * @param array $relatedRecord Related record as array
307
     * @param array $foreignTableTca TCA of the related table
308
     * @param string $foreignTableLabelField Field name of the foreign label field
309
     * @param ContentObjectRenderer $parentContentObject cObject
310
     * @param string $foreignTableName Related record table name
311
     *
312
     * @return string
313
     */
314 2
    protected function resolveRelatedValue(
315
        array $relatedRecord,
316
        $foreignTableTca,
317
        $foreignTableLabelField,
318
        ContentObjectRenderer $parentContentObject,
319
        $foreignTableName = ''
320
    ) {
321 2
        if ($GLOBALS['TSFE']->sys_language_uid > 0 && !empty($foreignTableName)) {
322
            $relatedRecord = $this->getTranslationOverlay($foreignTableName, $relatedRecord);
323
        }
324
325 2
        $value = $relatedRecord[$foreignTableLabelField];
326
327
        if (
328 2
            !empty($foreignTableName)
329 2
            && isset($foreignTableTca['columns'][$foreignTableLabelField]['config']['foreign_table'])
330 2
            && $this->configuration['enableRecursiveValueResolution']
331
        ) {
332
            // backup
333 1
            $backupRecord = $parentContentObject->data;
334 1
            $backupConfiguration = $this->configuration;
335
336
            // adjust configuration for next level
337 1
            $this->configuration['localField'] = $foreignTableLabelField;
338 1
            $parentContentObject->data = $relatedRecord;
0 ignored issues
show
Documentation Bug introduced by
It seems like $relatedRecord of type * is incompatible with the declared type array of property $data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
339 1
            if (strpos($this->configuration['foreignLabelField'], '.') !== false) {
340 1
                list($unusedDummy, $this->configuration['foreignLabelField']) = explode('.',
0 ignored issues
show
Unused Code introduced by
The assignment to $unusedDummy is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
341 1
                    $this->configuration['foreignLabelField'], 2);
342
            } else {
343 1
                $this->configuration['foreignLabelField'] = '';
344
            }
345
346
            // recursion
347 1
            $value = array_pop($this->getRelatedItemsFromForeignTable(
0 ignored issues
show
Bug introduced by
$this->getRelatedItemsFr..., $parentContentObject) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
348
                $foreignTableName,
349 1
                $relatedRecord['uid'],
350 1
                $foreignTableTca['columns'][$foreignTableLabelField],
351
                $parentContentObject
352
            ));
353
354
            // restore
355 1
            $this->configuration= $backupConfiguration;
356 1
            $parentContentObject->data = $backupRecord;
357
        }
358
359 2
        return $parentContentObject->stdWrap($value, $this->configuration);
360
    }
361
362
    /**
363
     * When the record has an overlay we retrieve the uid of the translated record,
364
     * to resolve the relations from the translation.
365
     *
366
     * @param string $localTableName
367
     * @param int $localRecordUid
368
     * @return int
369
     */
370 5
    protected function getUidOfRecordOverlay($localTableName, $localRecordUid)
371
    {
372
        // when no language is set at all we do not need to overlay
373 5
        if (!isset($GLOBALS['TSFE']->sys_language_uid)) {
374
            return $localRecordUid;
375
        }
376
        // when no language is set we can return the passed recordUid
377 5
        if (!$GLOBALS['TSFE']->sys_language_uid > 0) {
378 4
            return $localRecordUid;
379
        }
380
        /** @var  $db  \TYPO3\CMS\Core\Database\DatabaseConnection */
381 1
        $db = $GLOBALS['TYPO3_DB'];
382 1
        $record = $db->exec_SELECTgetSingleRow('*', $localTableName, 'uid = ' . $localRecordUid);
383
384
        // when the overlay is not an array, we return the localRecordUid
385 1
        if (!is_array($record)) {
386
            return $localRecordUid;
387
        }
388
389 1
        $record = $this->getTranslationOverlay($localTableName, $record);
390
        // when there is a _LOCALIZED_UID in the overlay, we return it
391 1
        $localRecordUid = $record['_LOCALIZED_UID'] ? $record['_LOCALIZED_UID'] : $localRecordUid;
392 1
        return $localRecordUid;
393
    }
394
395
    /**
396
     * Return records via relation.
397
     *
398
     * @param string $foreignTable The table to fetch records from.
399
     * @param array $uids The uids to fetch from table.
400
     * @param string $whereClause The where clause to append.
401
     *
402
     * @return array
403
     */
404 5
    protected function getRelatedRecords($foreignTable, array $uids, $whereClause)
405
    {
406 5
        if (isset($this->configuration['additionalWhereClause'])) {
407 2
            $whereClause .= ' AND ' . $this->configuration['additionalWhereClause'];
408
        }
409
410 5
        return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
411 5
            '*',
412
            $foreignTable,
413 5
            'uid IN (' . implode(',', $uids) . ')'
414 5
            . $whereClause
415
        );
416
    }
417
}
418