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