1<?php 2namespace TYPO3\CMS\Core\Category\Collection; 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\Collection\AbstractRecordCollection; 18use TYPO3\CMS\Core\Collection\CollectionInterface; 19use TYPO3\CMS\Core\Collection\EditableCollectionInterface; 20use TYPO3\CMS\Core\Database\ConnectionPool; 21use TYPO3\CMS\Core\Database\Query\QueryBuilder; 22use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; 23use TYPO3\CMS\Core\Utility\GeneralUtility; 24 25/** 26 * Category Collection to handle records attached to a category 27 */ 28class CategoryCollection extends AbstractRecordCollection implements EditableCollectionInterface 29{ 30 /** 31 * The table name collections are stored to 32 * 33 * @var string 34 */ 35 protected static $storageTableName = 'sys_category'; 36 37 /** 38 * Name of the categories-relation field (used in the MM_match_fields/fieldname property of the TCA) 39 * 40 * @var string 41 */ 42 protected $relationFieldName = 'categories'; 43 44 /** 45 * Creates this object. 46 * 47 * @param string $tableName Name of the table to be working on 48 * @param string $fieldName Name of the field where the categories relations are defined 49 * @throws \RuntimeException 50 */ 51 public function __construct($tableName = null, $fieldName = null) 52 { 53 parent::__construct(); 54 if (!empty($tableName)) { 55 $this->setItemTableName($tableName); 56 } elseif (empty($this->itemTableName)) { 57 throw new \RuntimeException(self::class . ' needs a valid itemTableName.', 1341826168); 58 } 59 if (!empty($fieldName)) { 60 $this->setRelationFieldName($fieldName); 61 } 62 } 63 64 /** 65 * Creates a new collection objects and reconstitutes the 66 * given database record to the new object. 67 * 68 * @param array $collectionRecord Database record 69 * @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections 70 * @return CategoryCollection 71 */ 72 public static function create(array $collectionRecord, $fillItems = false) 73 { 74 /** @var CategoryCollection $collection */ 75 $collection = GeneralUtility::makeInstance( 76 self::class, 77 $collectionRecord['table_name'], 78 $collectionRecord['field_name'] 79 ); 80 $collection->fromArray($collectionRecord); 81 if ($fillItems) { 82 $collection->loadContents(); 83 } 84 return $collection; 85 } 86 87 /** 88 * Loads the collections with the given id from persistence 89 * For memory reasons, per default only f.e. title, database-table, 90 * identifier (what ever static data is defined) is loaded. 91 * Entries can be load on first access. 92 * 93 * @param int $id Id of database record to be loaded 94 * @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections 95 * @param string $tableName Name of table from which entries should be loaded 96 * @param string $fieldName Name of the categories relation field 97 * @return CollectionInterface 98 */ 99 public static function load($id, $fillItems = false, $tableName = '', $fieldName = '') 100 { 101 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) 102 ->getQueryBuilderForTable(static::$storageTableName); 103 104 $queryBuilder->getRestrictions() 105 ->removeAll() 106 ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); 107 108 $collectionRecord = $queryBuilder->select('*') 109 ->from(static::$storageTableName) 110 ->where( 111 $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)) 112 ) 113 ->setMaxResults(1) 114 ->execute() 115 ->fetch(); 116 117 $collectionRecord['table_name'] = $tableName; 118 $collectionRecord['field_name'] = $fieldName; 119 120 return self::create($collectionRecord, $fillItems); 121 } 122 123 /** 124 * Selects the collected records in this collection, by 125 * looking up the MM relations of this record to the 126 * table name defined in the local field 'table_name'. 127 * 128 * @return QueryBuilder 129 */ 130 protected function getCollectedRecordsQueryBuilder() 131 { 132 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) 133 ->getQueryBuilderForTable(static::$storageTableName); 134 $queryBuilder->getRestrictions()->removeAll(); 135 136 $queryBuilder->select($this->getItemTableName() . '.*') 137 ->from(static::$storageTableName) 138 ->join( 139 static::$storageTableName, 140 'sys_category_record_mm', 141 'sys_category_record_mm', 142 $queryBuilder->expr()->eq( 143 'sys_category_record_mm.uid_local', 144 $queryBuilder->quoteIdentifier(static::$storageTableName . '.uid') 145 ) 146 ) 147 ->join( 148 'sys_category_record_mm', 149 $this->getItemTableName(), 150 $this->getItemTableName(), 151 $queryBuilder->expr()->eq( 152 'sys_category_record_mm.uid_foreign', 153 $queryBuilder->quoteIdentifier($this->getItemTableName() . '.uid') 154 ) 155 ) 156 ->where( 157 $queryBuilder->expr()->eq( 158 static::$storageTableName . '.uid', 159 $queryBuilder->createNamedParameter($this->getIdentifier(), \PDO::PARAM_INT) 160 ), 161 $queryBuilder->expr()->eq( 162 'sys_category_record_mm.tablenames', 163 $queryBuilder->createNamedParameter($this->getItemTableName(), \PDO::PARAM_STR) 164 ), 165 $queryBuilder->expr()->eq( 166 'sys_category_record_mm.fieldname', 167 $queryBuilder->createNamedParameter($this->getRelationFieldName(), \PDO::PARAM_STR) 168 ) 169 ); 170 171 return $queryBuilder; 172 } 173 174 /** 175 * Gets the collected records in this collection, by 176 * using <getCollectedRecordsQueryBuilder>. 177 * 178 * @return array 179 */ 180 protected function getCollectedRecords() 181 { 182 $relatedRecords = []; 183 184 $queryBuilder = $this->getCollectedRecordsQueryBuilder(); 185 $result = $queryBuilder->execute(); 186 187 while ($record = $result->fetch()) { 188 $relatedRecords[] = $record; 189 } 190 191 return $relatedRecords; 192 } 193 194 /** 195 * Populates the content-entries of the storage 196 * Queries the underlying storage for entries of the collection 197 * and adds them to the collection data. 198 * If the content entries of the storage had not been loaded on creation 199 * ($fillItems = false) this function is to be used for loading the contents 200 * afterwards. 201 */ 202 public function loadContents() 203 { 204 $entries = $this->getCollectedRecords(); 205 $this->removeAll(); 206 foreach ($entries as $entry) { 207 $this->add($entry); 208 } 209 } 210 211 /** 212 * Returns an array of the persistable properties and contents 213 * which are processable by DataHandler. 214 * for internal usage in persist only. 215 * 216 * @return array 217 */ 218 protected function getPersistableDataArray() 219 { 220 return [ 221 'title' => $this->getTitle(), 222 'description' => $this->getDescription(), 223 'items' => $this->getItemUidList(true) 224 ]; 225 } 226 227 /** 228 * Adds on entry to the collection 229 * 230 * @param mixed $data 231 */ 232 public function add($data) 233 { 234 $this->storage->push($data); 235 } 236 237 /** 238 * Adds a set of entries to the collection 239 * 240 * @param CollectionInterface $other 241 */ 242 public function addAll(CollectionInterface $other) 243 { 244 foreach ($other as $value) { 245 $this->add($value); 246 } 247 } 248 249 /** 250 * Removes the given entry from collection 251 * Note: not the given "index" 252 * 253 * @param mixed $data 254 */ 255 public function remove($data) 256 { 257 $offset = 0; 258 foreach ($this->storage as $value) { 259 if ($value == $data) { 260 break; 261 } 262 $offset++; 263 } 264 $this->storage->offsetUnset($offset); 265 } 266 267 /** 268 * Removes all entries from the collection 269 * collection will be empty afterwards 270 */ 271 public function removeAll() 272 { 273 $this->storage = new \SplDoublyLinkedList(); 274 } 275 276 /** 277 * Gets the current available items. 278 * 279 * @return array 280 */ 281 public function getItems() 282 { 283 $itemArray = []; 284 /** @var \TYPO3\CMS\Core\Resource\File $item */ 285 foreach ($this->storage as $item) { 286 $itemArray[] = $item; 287 } 288 return $itemArray; 289 } 290 291 /** 292 * Sets the name of the categories relation field 293 * 294 * @param string $field 295 */ 296 public function setRelationFieldName($field) 297 { 298 $this->relationFieldName = $field; 299 } 300 301 /** 302 * Gets the name of the categories relation field 303 * 304 * @return string 305 */ 306 public function getRelationFieldName() 307 { 308 return $this->relationFieldName; 309 } 310 311 /** 312 * Getter for the storage table name 313 * 314 * @return string 315 */ 316 public static function getStorageTableName() 317 { 318 return self::$storageTableName; 319 } 320 321 /** 322 * Getter for the storage items field 323 * 324 * @return string 325 */ 326 public static function getStorageItemsField() 327 { 328 return self::$storageItemsField; 329 } 330} 331