[BUGFIX] mssql: ExpressionBuilder inSet() support
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Index / MetaDataRepository.php
1 <?php
2
3 namespace TYPO3\CMS\Core\Resource\Index;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Database\Connection;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
21 use TYPO3\CMS\Core\Resource\Exception\InvalidUidException;
22 use TYPO3\CMS\Core\Resource\File;
23 use TYPO3\CMS\Core\SingletonInterface;
24 use TYPO3\CMS\Core\Type\File as FileType;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Extbase\Object\ObjectManager;
27 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
28
29 /**
30 * Repository Class as an abstraction layer to sys_file_metadata
31 *
32 * Every access to table sys_file_metadata which is not handled by DataHandler
33 * has to use this Repository class
34 */
35 class MetaDataRepository implements SingletonInterface
36 {
37 /**
38 * @var string
39 */
40 protected $tableName = 'sys_file_metadata';
41
42 /**
43 * Internal storage for database table fields
44 *
45 * @var array
46 */
47 protected $tableFields = [];
48
49 /**
50 * Returns array of meta-data properties
51 *
52 * @param File $file
53 * @return array
54 */
55 public function findByFile(File $file)
56 {
57 $record = $this->findByFileUid($file->getUid());
58
59 // It could be possible that the meta information is freshly
60 // created and inserted into the database. If this is the case
61 // we have to take care about correct meta information for width and
62 // height in case of an image.
63 if (!empty($record['newlyCreated'])) {
64 if ($file->getType() === File::FILETYPE_IMAGE && $file->getStorage()->getDriverType() === 'Local') {
65 $fileNameAndPath = $file->getForLocalProcessing(false);
66
67 $imageInfo = GeneralUtility::makeInstance(FileType\ImageInfo::class, $fileNameAndPath);
68
69 $additionalMetaInformation = [
70 'width' => $imageInfo->getWidth(),
71 'height' => $imageInfo->getHeight(),
72 ];
73
74 $this->update($file->getUid(), $additionalMetaInformation);
75 }
76 $record = $this->findByFileUid($file->getUid());
77 }
78
79 return $record;
80 }
81
82 /**
83 * Retrieves metadata for file
84 *
85 * @param int $uid
86 * @return array
87 * @throws InvalidUidException
88 */
89 public function findByFileUid($uid)
90 {
91 $uid = (int)$uid;
92 if ($uid <= 0) {
93 throw new InvalidUidException('Metadata can only be retrieved for indexed files. UID: "' . $uid . '"', 1381590731);
94 }
95
96 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->tableName);
97
98 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
99
100 $record = $queryBuilder
101 ->select('*')
102 ->from($this->tableName)
103 ->where(
104 $queryBuilder->expr()->eq('file', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
105 $queryBuilder->expr()->in('sys_language_uid', $queryBuilder->createNamedParameter([0, -1], Connection::PARAM_INT_ARRAY))
106 )
107 ->execute()
108 ->fetch();
109
110 if (empty($record)) {
111 $record = $this->createMetaDataRecord($uid);
112 }
113
114 $passedData = new \ArrayObject($record);
115
116 $this->emitRecordPostRetrievalSignal($passedData);
117 return $passedData->getArrayCopy();
118 }
119
120 /**
121 * Create empty
122 *
123 * @param int $fileUid
124 * @param array $additionalFields
125 * @return array
126 */
127 public function createMetaDataRecord($fileUid, array $additionalFields = [])
128 {
129 $emptyRecord = [
130 'file' => (int)$fileUid,
131 'pid' => 0,
132 'crdate' => $GLOBALS['EXEC_TIME'],
133 'tstamp' => $GLOBALS['EXEC_TIME'],
134 'cruser_id' => isset($GLOBALS['BE_USER']->user['uid']) ? (int)$GLOBALS['BE_USER']->user['uid'] : 0,
135 'l10n_diffsource' => ''
136 ];
137 $emptyRecord = array_merge($emptyRecord, $additionalFields);
138
139 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName);
140 $connection->insert(
141 $this->tableName,
142 $emptyRecord,
143 ['l10n_diffsource' => Connection::PARAM_LOB]
144 );
145
146 $record = $emptyRecord;
147 $record['uid'] = $connection->lastInsertId($this->tableName);
148 $record['newlyCreated'] = true;
149
150 $this->emitRecordCreatedSignal($record);
151
152 return $record;
153 }
154
155 /**
156 * Updates the metadata record in the database
157 *
158 * @param int $fileUid the file uid to update
159 * @param array $data Data to update
160 * @internal
161 */
162 public function update($fileUid, array $data)
163 {
164 if (empty($this->tableFields)) {
165 $this->tableFields = GeneralUtility::makeInstance(ConnectionPool::class)
166 ->getConnectionForTable($this->tableName)
167 ->getSchemaManager()
168 ->listTableColumns($this->tableName);
169 }
170 $updateRow = array_intersect_key($data, $this->tableFields);
171 if (array_key_exists('uid', $updateRow)) {
172 unset($updateRow['uid']);
173 }
174 $row = $this->findByFileUid($fileUid);
175 if (!empty($updateRow)) {
176 $updateRow['tstamp'] = time();
177 GeneralUtility::makeInstance(ConnectionPool::class)
178 ->getConnectionForTable($this->tableName)
179 ->update(
180 $this->tableName,
181 $updateRow,
182 [
183 'uid' => (int)$row['uid']
184 ]
185 );
186
187 $this->emitRecordUpdatedSignal(array_merge($row, $updateRow));
188 }
189 }
190
191 /**
192 * Remove all metadata records for a certain file from the database
193 *
194 * @param int $fileUid
195 */
196 public function removeByFileUid($fileUid)
197 {
198 GeneralUtility::makeInstance(ConnectionPool::class)
199 ->getConnectionForTable($this->tableName)
200 ->delete(
201 $this->tableName,
202 [
203 'file' => (int)$fileUid
204 ]
205 );
206 $this->emitRecordDeletedSignal($fileUid);
207 }
208
209 /**
210 * Get the SignalSlot dispatcher
211 *
212 * @return Dispatcher
213 */
214 protected function getSignalSlotDispatcher()
215 {
216 return $this->getObjectManager()->get(Dispatcher::class);
217 }
218
219 /**
220 * Get the ObjectManager
221 *
222 * @return ObjectManager
223 */
224 protected function getObjectManager()
225 {
226 return GeneralUtility::makeInstance(ObjectManager::class);
227 }
228
229 /**
230 * Signal that is called after a record has been loaded from database
231 * Allows other places to do extension of metadata at runtime or
232 * for example translation and workspace overlay
233 *
234 * @param \ArrayObject $data
235 * @signal
236 */
237 protected function emitRecordPostRetrievalSignal(\ArrayObject $data)
238 {
239 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordPostRetrieval', [$data]);
240 }
241
242 /**
243 * Signal that is called after an IndexRecord is updated
244 *
245 * @param array $data
246 * @signal
247 */
248 protected function emitRecordUpdatedSignal(array $data)
249 {
250 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordUpdated', [$data]);
251 }
252
253 /**
254 * Signal that is called after an IndexRecord is created
255 *
256 * @param array $data
257 * @signal
258 */
259 protected function emitRecordCreatedSignal(array $data)
260 {
261 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordCreated', [$data]);
262 }
263
264 /**
265 * Signal that is called after an IndexRecord is deleted
266 *
267 * @param int $fileUid
268 * @signal
269 */
270 protected function emitRecordDeletedSignal($fileUid)
271 {
272 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordDeleted', [$fileUid]);
273 }
274
275 /**
276 * @return MetaDataRepository
277 */
278 public static function getInstance()
279 {
280 return GeneralUtility::makeInstance(self::class);
281 }
282 }