1<?php 2namespace TYPO3\CMS\Frontend\ContentObject; 3 4/* 5 * This file is part of the TYPO3 CMS project. 6 * 7 * It is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License, either version 2 9 * of the License, or any later version. 10 * 11 * For the full copyright and license information, please read the 12 * LICENSE.txt file that was distributed with this source code. 13 * 14 * The TYPO3 project - inspiring people to share! 15 */ 16 17use TYPO3\CMS\Core\Database\RelationHandler; 18use TYPO3\CMS\Core\TimeTracker\TimeTracker; 19use TYPO3\CMS\Core\Utility\GeneralUtility; 20use TYPO3\CMS\Frontend\Category\Collection\CategoryCollection; 21use TYPO3\CMS\Frontend\Page\PageRepository; 22 23/** 24 * Contains RECORDS class object. 25 */ 26class RecordsContentObject extends AbstractContentObject 27{ 28 /** 29 * List of all items with table and uid information 30 * 31 * @var array 32 */ 33 protected $itemArray = []; 34 35 /** 36 * List of all selected records with full data, arranged per table 37 * 38 * @var array 39 */ 40 protected $data = []; 41 42 /** 43 * Rendering the cObject, RECORDS 44 * 45 * @param array $conf Array of TypoScript properties 46 * @return string Output 47 */ 48 public function render($conf = []) 49 { 50 // Reset items and data 51 $this->itemArray = []; 52 $this->data = []; 53 54 $theValue = ''; 55 $originalRec = $GLOBALS['TSFE']->currentRecord; 56 // If the currentRecord is set, we register, that this record has invoked this function. 57 // It's should not be allowed to do this again then!! 58 if ($originalRec) { 59 ++$GLOBALS['TSFE']->recordRegister[$originalRec]; 60 } 61 62 $tables = isset($conf['tables.']) ? $this->cObj->stdWrap($conf['tables'], $conf['tables.']) : $conf['tables']; 63 if ($tables) { 64 $tablesArray = array_unique(GeneralUtility::trimExplode(',', $tables, true)); 65 // Add tables which have a configuration (note that this may create duplicate entries) 66 if (is_array($conf['conf.'])) { 67 foreach ($conf['conf.'] as $key => $value) { 68 if (substr($key, -1) !== '.' && !in_array($key, $tablesArray)) { 69 $tablesArray[] = $key; 70 } 71 } 72 } 73 74 // Get the data, depending on collection method. 75 // Property "source" is considered more precise and thus takes precedence over "categories" 76 $source = isset($conf['source.']) ? $this->cObj->stdWrap($conf['source'], $conf['source.']) : $conf['source']; 77 $categories = isset($conf['categories.']) ? $this->cObj->stdWrap($conf['categories'], $conf['categories.']) : $conf['categories']; 78 if ($source) { 79 $this->collectRecordsFromSource($source, $tablesArray); 80 } elseif ($categories) { 81 $relationField = isset($conf['categories.']['relation.']) ? $this->cObj->stdWrap($conf['categories.']['relation'], $conf['categories.']['relation.']) : $conf['categories.']['relation']; 82 $this->collectRecordsFromCategories($categories, $tablesArray, $relationField); 83 } 84 $itemArrayCount = count($this->itemArray); 85 if ($itemArrayCount > 0) { 86 /** @var ContentObjectRenderer $cObj */ 87 $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class); 88 $cObj->setParent($this->cObj->data, $this->cObj->currentRecord); 89 $this->cObj->currentRecordNumber = 0; 90 $this->cObj->currentRecordTotal = $itemArrayCount; 91 foreach ($this->itemArray as $val) { 92 $row = $this->data[$val['table']][$val['id']]; 93 // Perform overlays if necessary (records coming from category collections are already overlaid) 94 if ($source) { 95 // Versioning preview 96 $this->getPageRepository()->versionOL($val['table'], $row); 97 // Language overlay 98 if (is_array($row)) { 99 $row = $this->getPageRepository()->getLanguageOverlay($val['table'], $row); 100 } 101 } 102 // Might be unset during the overlay process 103 if (is_array($row)) { 104 $dontCheckPid = isset($conf['dontCheckPid.']) ? $this->cObj->stdWrap($conf['dontCheckPid'], $conf['dontCheckPid.']) : $conf['dontCheckPid']; 105 if (!$dontCheckPid) { 106 $row = $this->cObj->checkPid($row['pid']) ? $row : ''; 107 } 108 if ($row && !$GLOBALS['TSFE']->recordRegister[$val['table'] . ':' . $val['id']]) { 109 $renderObjName = $conf['conf.'][$val['table']] ?: '<' . $val['table']; 110 $renderObjKey = $conf['conf.'][$val['table']] ? 'conf.' . $val['table'] : ''; 111 $renderObjConf = $conf['conf.'][$val['table'] . '.']; 112 $this->cObj->currentRecordNumber++; 113 $cObj->parentRecordNumber = $this->cObj->currentRecordNumber; 114 $GLOBALS['TSFE']->currentRecord = $val['table'] . ':' . $val['id']; 115 $this->cObj->lastChanged($row['tstamp']); 116 $cObj->start($row, $val['table']); 117 $tmpValue = $cObj->cObjGetSingle($renderObjName, $renderObjConf, $renderObjKey); 118 $theValue .= $tmpValue; 119 } 120 } 121 } 122 } 123 } 124 $wrap = isset($conf['wrap.']) ? $this->cObj->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap']; 125 if ($wrap) { 126 $theValue = $this->cObj->wrap($theValue, $wrap); 127 } 128 if (isset($conf['stdWrap.'])) { 129 $theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']); 130 } 131 // Restore 132 $GLOBALS['TSFE']->currentRecord = $originalRec; 133 if ($originalRec) { 134 --$GLOBALS['TSFE']->recordRegister[$originalRec]; 135 } 136 return $theValue; 137 } 138 139 /** 140 * Collects records according to the configured source 141 * 142 * @param string $source Source of records 143 * @param array $tables List of tables 144 */ 145 protected function collectRecordsFromSource($source, array $tables) 146 { 147 /** @var RelationHandler $loadDB*/ 148 $loadDB = GeneralUtility::makeInstance(RelationHandler::class); 149 $loadDB->setFetchAllFields(true); 150 $loadDB->start($source, implode(',', $tables)); 151 foreach ($loadDB->tableArray as $table => $v) { 152 if (isset($GLOBALS['TCA'][$table])) { 153 $loadDB->additionalWhere[$table] = $this->getPageRepository()->enableFields($table); 154 } 155 } 156 $this->data = $loadDB->getFromDB(); 157 reset($loadDB->itemArray); 158 $this->itemArray = $loadDB->itemArray; 159 } 160 161 /** 162 * Collects records for all selected tables and categories. 163 * 164 * @param string $selectedCategories Comma-separated list of categories 165 * @param array $tables List of tables 166 * @param string $relationField Name of the field containing the categories relation 167 */ 168 protected function collectRecordsFromCategories($selectedCategories, array $tables, $relationField) 169 { 170 $selectedCategories = array_unique(GeneralUtility::intExplode(',', $selectedCategories, true)); 171 172 // Loop on all selected tables 173 foreach ($tables as $table) { 174 175 // Get the records for each selected category 176 $tableRecords = []; 177 $categoriesPerRecord = []; 178 foreach ($selectedCategories as $aCategory) { 179 try { 180 $collection = CategoryCollection::load( 181 $aCategory, 182 true, 183 $table, 184 $relationField 185 ); 186 if ($collection->count() > 0) { 187 // Add items to the collection of records for the current table 188 foreach ($collection as $item) { 189 $tableRecords[$item['uid']] = $item; 190 // Keep track of all categories a given item belongs to 191 if (!isset($categoriesPerRecord[$item['uid']])) { 192 $categoriesPerRecord[$item['uid']] = []; 193 } 194 $categoriesPerRecord[$item['uid']][] = $aCategory; 195 } 196 } 197 } catch (\Exception $e) { 198 $message = sprintf( 199 'Could not get records for category id %d. Error: %s (%d)', 200 $aCategory, 201 $e->getMessage(), 202 $e->getCode() 203 ); 204 $this->getTimeTracker()->setTSlogMessage($message, 2); 205 } 206 } 207 // Store the resulting records into the itemArray and data results array 208 if (!empty($tableRecords)) { 209 $this->data[$table] = []; 210 foreach ($tableRecords as $record) { 211 $this->itemArray[] = [ 212 'id' => $record['uid'], 213 'table' => $table 214 ]; 215 // Add to the record the categories it belongs to 216 $record['_categories'] = implode(',', $categoriesPerRecord[$record['uid']]); 217 $this->data[$table][$record['uid']] = $record; 218 } 219 } 220 } 221 } 222 223 /** 224 * @return TimeTracker 225 */ 226 protected function getTimeTracker() 227 { 228 return GeneralUtility::makeInstance(TimeTracker::class); 229 } 230 231 /** 232 * @return PageRepository 233 */ 234 protected function getPageRepository(): PageRepository 235 { 236 return $GLOBALS['TSFE']->sys_page ?: GeneralUtility::makeInstance(PageRepository::class); 237 } 238} 239