[TASK] Allow multiple extractor services with the same priority
[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 return [];
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 $additionalFields = array_intersect_key($additionalFields, $this->getTableFields());
139 $emptyRecord = array_merge($emptyRecord, $additionalFields);
140
141 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName);
142 $connection->insert(
143 $this->tableName,
144 $emptyRecord,
145 ['l10n_diffsource' => Connection::PARAM_LOB]
146 );
147
148 $record = $emptyRecord;
149 $record['uid'] = $connection->lastInsertId($this->tableName);
150 $record['newlyCreated'] = true;
151
152 $this->emitRecordCreatedSignal($record);
153
154 return $record;
155 }
156
157 /**
158 * Updates the metadata record in the database
159 *
160 * @param int $fileUid the file uid to update
161 * @param array $data Data to update
162 * @internal
163 */
164 public function update($fileUid, array $data)
165 {
166 $updateRow = array_intersect_key($data, $this->getTableFields());
167 if (array_key_exists('uid', $updateRow)) {
168 unset($updateRow['uid']);
169 }
170 $row = $this->findByFileUid($fileUid);
171 if (!empty($updateRow)) {
172 $updateRow['tstamp'] = time();
173 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName);
174 $types = [];
175 if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) {
176 // mssql needs to set proper PARAM_LOB and others to update fields
177 $tableDetails = $connection->getSchemaManager()->listTableDetails($this->tableName);
178 foreach ($updateRow as $columnName => $columnValue) {
179 $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
180 }
181 }
182 $connection->update(
183 $this->tableName,
184 $updateRow,
185 [
186 'uid' => (int)$row['uid']
187 ],
188 $types
189 );
190
191 $this->emitRecordUpdatedSignal(array_merge($row, $updateRow));
192 }
193 }
194
195 /**
196 * Remove all metadata records for a certain file from the database
197 *
198 * @param int $fileUid
199 */
200 public function removeByFileUid($fileUid)
201 {
202 GeneralUtility::makeInstance(ConnectionPool::class)
203 ->getConnectionForTable($this->tableName)
204 ->delete(
205 $this->tableName,
206 [
207 'file' => (int)$fileUid
208 ]
209 );
210 $this->emitRecordDeletedSignal($fileUid);
211 }
212
213 /**
214 * Get the SignalSlot dispatcher
215 *
216 * @return Dispatcher
217 */
218 protected function getSignalSlotDispatcher()
219 {
220 return $this->getObjectManager()->get(Dispatcher::class);
221 }
222
223 /**
224 * Get the ObjectManager
225 *
226 * @return ObjectManager
227 */
228 protected function getObjectManager()
229 {
230 return GeneralUtility::makeInstance(ObjectManager::class);
231 }
232
233 /**
234 * Signal that is called after a record has been loaded from database
235 * Allows other places to do extension of metadata at runtime or
236 * for example translation and workspace overlay
237 *
238 * @param \ArrayObject $data
239 */
240 protected function emitRecordPostRetrievalSignal(\ArrayObject $data)
241 {
242 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordPostRetrieval', [$data]);
243 }
244
245 /**
246 * Signal that is called after an IndexRecord is updated
247 *
248 * @param array $data
249 */
250 protected function emitRecordUpdatedSignal(array $data)
251 {
252 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordUpdated', [$data]);
253 }
254
255 /**
256 * Signal that is called after an IndexRecord is created
257 *
258 * @param array $data
259 */
260 protected function emitRecordCreatedSignal(array $data)
261 {
262 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordCreated', [$data]);
263 }
264
265 /**
266 * Signal that is called after an IndexRecord is deleted
267 *
268 * @param int $fileUid
269 */
270 protected function emitRecordDeletedSignal($fileUid)
271 {
272 $this->getSignalSlotDispatcher()->dispatch(self::class, 'recordDeleted', [$fileUid]);
273 }
274
275 /**
276 * Gets the fields that are available in the table
277 *
278 * @return array
279 */
280 protected function getTableFields(): array
281 {
282 if (empty($this->tableFields)) {
283 $this->tableFields = GeneralUtility::makeInstance(ConnectionPool::class)
284 ->getConnectionForTable($this->tableName)
285 ->getSchemaManager()
286 ->listTableColumns($this->tableName);
287 }
288
289 return $this->tableFields;
290 }
291
292 /**
293 * @return MetaDataRepository
294 */
295 public static function getInstance()
296 {
297 return GeneralUtility::makeInstance(self::class);
298 }
299 }