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