[TASK] Removes extra empty lines
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Index / FileIndexRepository.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource\Index;
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
17 use TYPO3\CMS\Core\Database\ReferenceIndex;
18 use TYPO3\CMS\Core\Resource\File;
19 use TYPO3\CMS\Core\SingletonInterface;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Repository Class as an abstraction layer to sys_file
24 *
25 * Every access to table sys_file_metadata which is not handled by TCEmain
26 * has to use this Repository class.
27 *
28 * This is meant for FAL internal use only!.
29 */
30 class FileIndexRepository implements SingletonInterface
31 {
32 /**
33 * @var string
34 */
35 protected $table = 'sys_file';
36
37 /**
38 * A list of properties which are to be persisted
39 *
40 * @var array
41 */
42 protected $fields = array(
43 'uid', 'pid', 'missing', 'type', 'storage', 'identifier', 'identifier_hash', 'extension',
44 'mime_type', 'name', 'sha1', 'size', 'creation_date', 'modification_date', 'folder_hash'
45 );
46
47 /**
48 * Gets database instance
49 *
50 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
51 */
52 protected function getDatabaseConnection()
53 {
54 return $GLOBALS['TYPO3_DB'];
55 }
56
57 /**
58 * Gets the Resource Factory
59 *
60 * @return \TYPO3\CMS\Core\Resource\ResourceFactory
61 */
62 protected function getResourceFactory()
63 {
64 return \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
65 }
66
67 /**
68 * Returns an Instance of the Repository
69 *
70 * @return FileIndexRepository
71 */
72 public static function getInstance()
73 {
74 return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\FileIndexRepository::class);
75 }
76
77 /**
78 * Retrieves Index record for a given $combinedIdentifier
79 *
80 * @param string $combinedIdentifier
81 * @return array|boolean
82 */
83 public function findOneByCombinedIdentifier($combinedIdentifier)
84 {
85 list($storageUid, $identifier) = GeneralUtility::trimExplode(':', $combinedIdentifier, false, 2);
86 return $this->findOneByStorageUidAndIdentifier($storageUid, $identifier);
87 }
88
89 /**
90 * Retrieves Index record for a given $fileUid
91 *
92 * @param int $fileUid
93 * @return array|boolean
94 */
95 public function findOneByUid($fileUid)
96 {
97 $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
98 implode(',', $this->fields),
99 $this->table,
100 'uid=' . (int)$fileUid
101 );
102 return is_array($row) ? $row : false;
103 }
104
105 /**
106 * Retrieves Index record for a given $storageUid and $identifier
107 *
108 * @param int $storageUid
109 * @param string $identifier
110 * @return array|boolean
111 *
112 * @internal only for use from FileRepository
113 */
114 public function findOneByStorageUidAndIdentifier($storageUid, $identifier)
115 {
116 $identifierHash = $this->getResourceFactory()->getStorageObject($storageUid)->hashFileIdentifier($identifier);
117 return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
118 }
119
120 /**
121 * Retrieves Index record for a given $storageUid and $identifier
122 *
123 * @param int $storageUid
124 * @param string $identifierHash
125 * @return array|boolean
126 *
127 * @internal only for use from FileRepository
128 */
129 public function findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash)
130 {
131 $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
132 implode(',', $this->fields),
133 $this->table,
134 sprintf('storage=%u AND identifier_hash=%s', (int)$storageUid, $this->getDatabaseConnection()->fullQuoteStr($identifierHash, $this->table))
135 );
136 return is_array($row) ? $row : false;
137 }
138
139 /**
140 * Retrieves Index record for a given $fileObject
141 *
142 * @param \TYPO3\CMS\Core\Resource\FileInterface $fileObject
143 * @return array|boolean
144 *
145 * @internal only for use from FileRepository
146 */
147 public function findOneByFileObject(\TYPO3\CMS\Core\Resource\FileInterface $fileObject)
148 {
149 $storageUid = $fileObject->getStorage()->getUid();
150 $identifierHash = $fileObject->getHashedIdentifier();
151 return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
152 }
153
154 /**
155 * Returns all indexed files which match the content hash
156 * Used by the indexer to detect already present files
157 *
158 * @param string $hash
159 * @return mixed
160 */
161 public function findByContentHash($hash)
162 {
163 if (!preg_match('/^[0-9a-f]{40}$/i', $hash)) {
164 return array();
165 }
166 $resultRows = $this->getDatabaseConnection()->exec_SELECTgetRows(
167 implode(',', $this->fields),
168 $this->table,
169 'sha1=' . $this->getDatabaseConnection()->fullQuoteStr($hash, $this->table)
170 );
171 return $resultRows;
172 }
173
174 /**
175 * Find all records for files in a Folder
176 *
177 * @param \TYPO3\CMS\Core\Resource\Folder $folder
178 * @return array|NULL
179 */
180 public function findByFolder(\TYPO3\CMS\Core\Resource\Folder $folder)
181 {
182 $resultRows = $this->getDatabaseConnection()->exec_SELECTgetRows(
183 implode(',', $this->fields),
184 $this->table,
185 'folder_hash = ' . $this->getDatabaseConnection()->fullQuoteStr($folder->getHashedIdentifier(), $this->table) .
186 ' AND storage = ' . (int)$folder->getStorage()->getUid(),
187 '',
188 '',
189 '',
190 'identifier'
191 );
192 return $resultRows;
193 }
194
195 /**
196 * Find all records for files in an array of Folders
197 *
198 * @param \TYPO3\CMS\Core\Resource\Folder[] $folders
199 * @param bool $includeMissing
200 * @return array|NULL
201 */
202 public function findByFolders(array $folders, $includeMissing = true)
203 {
204 $storageUids = [];
205 $folderIdentifiers = [];
206
207 foreach ($folders as $folder) {
208 if (!$folder instanceof \TYPO3\CMS\Core\Resource\Folder) {
209 continue;
210 }
211
212 $storageUids[] = (int)$folder->getStorage()->getUid();
213 $folderIdentifiers[] = $folder->getHashedIdentifier();
214 }
215 $storageUids = array_unique($storageUids);
216 $folderIdentifiers = array_unique($folderIdentifiers);
217
218 $fileRecords = $this->getDatabaseConnection()->exec_SELECTgetRows(
219 implode(',', $this->fields),
220 $this->table,
221 'folder_hash IN ( ' . implode(',', $this->getDatabaseConnection()->fullQuoteArray($folderIdentifiers, $this->table)) . ')' .
222 ' AND storage IN (' . implode(',', $storageUids) . ')' .
223 ($includeMissing ? '' : ' AND missing = 0'),
224 '',
225 '',
226 '',
227 'identifier'
228 );
229
230 return $fileRecords;
231 }
232
233 /**
234 * Adds a file to the index
235 *
236 * @param File $file
237 * @return void
238 */
239 public function add(File $file)
240 {
241 if ($this->hasIndexRecord($file)) {
242 $this->update($file);
243 if ($file->_getPropertyRaw('uid') === null) {
244 $file->updateProperties($this->findOneByFileObject($file));
245 }
246 } else {
247 $file->updateProperties(array('uid' => $this->insertRecord($file->getProperties())));
248 }
249 }
250
251 /**
252 * Add data from record (at indexing time)
253 *
254 * @param array $data
255 * @return array
256 */
257 public function addRaw(array $data)
258 {
259 $data['uid'] = $this->insertRecord($data);
260 return $data;
261 }
262
263 /**
264 * Helper to reduce code duplication
265 *
266 * @param array $data
267 *
268 * @return int
269 */
270 protected function insertRecord(array $data)
271 {
272 $data = array_intersect_key($data, array_flip($this->fields));
273 $data['tstamp'] = time();
274 $this->getDatabaseConnection()->exec_INSERTquery($this->table, $data);
275 $data['uid'] = $this->getDatabaseConnection()->sql_insert_id();
276 $this->updateRefIndex($data['uid']);
277 $this->emitRecordCreatedSignal($data);
278 return $data['uid'];
279 }
280 /**
281 * Checks if a file is indexed
282 *
283 * @param File $file
284 * @return bool
285 */
286 public function hasIndexRecord(File $file)
287 {
288 return $this->getDatabaseConnection()->exec_SELECTcountRows('uid', $this->table, $this->getWhereClauseForFile($file)) >= 1;
289 }
290
291 /**
292 * Updates the index record in the database
293 *
294 * @param File $file
295 * @return void
296 */
297 public function update(File $file)
298 {
299 $updatedProperties = array_intersect($this->fields, $file->getUpdatedProperties());
300 $updateRow = array();
301 foreach ($updatedProperties as $key) {
302 $updateRow[$key] = $file->getProperty($key);
303 }
304 if (!empty($updateRow)) {
305 $updateRow['tstamp'] = time();
306 $this->getDatabaseConnection()->exec_UPDATEquery($this->table, $this->getWhereClauseForFile($file), $updateRow);
307 $this->updateRefIndex($file->getUid());
308 $this->emitRecordUpdatedSignal(array_intersect_key($file->getProperties(), array_flip($this->fields)));
309 }
310 }
311
312 /**
313 * Finds the files needed for second indexer step
314 *
315 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
316 * @param int $limit
317 * @return array
318 */
319 public function findInStorageWithIndexOutstanding(\TYPO3\CMS\Core\Resource\ResourceStorage $storage, $limit = -1)
320 {
321 return $this->getDatabaseConnection()->exec_SELECTgetRows(
322 implode(',', $this->fields),
323 $this->table,
324 'tstamp > last_indexed AND storage = ' . (int)$storage->getUid(),
325 '',
326 'tstamp ASC',
327 (int)$limit > 0 ? (int)$limit : ''
328 );
329 }
330
331 /**
332 * Helper function for the Indexer to detect missing files
333 *
334 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
335 * @param array $uidList
336 * @return array
337 */
338 public function findInStorageAndNotInUidList(\TYPO3\CMS\Core\Resource\ResourceStorage $storage, array $uidList)
339 {
340 $where = 'storage = ' . (int)$storage->getUid();
341 if (!empty($uidList)) {
342 $where .= ' AND uid NOT IN (' . implode(',', $this->getDatabaseConnection()->cleanIntArray($uidList)) . ')';
343 }
344 return $this->getDatabaseConnection()->exec_SELECTgetRows(implode(',', $this->fields), $this->table, $where);
345 }
346
347 /**
348 * Updates the timestamp when the file indexer extracted metadata
349 *
350 * @param int $fileUid
351 * @return void
352 */
353 public function updateIndexingTime($fileUid)
354 {
355 $this->getDatabaseConnection()->exec_UPDATEquery($this->table, 'uid = ' . (int)$fileUid, array('last_indexed' => time()));
356 }
357
358 /**
359 * Marks given file as missing in sys_file
360 *
361 * @param int $fileUid
362 * @return void
363 */
364 public function markFileAsMissing($fileUid)
365 {
366 $this->getDatabaseConnection()->exec_UPDATEquery($this->table, 'uid = ' . (int)$fileUid, array('missing' => 1));
367 $this->emitRecordMarkedAsMissingSignal($fileUid);
368 }
369
370 /**
371 * Returns a where clause to find a file in database
372 *
373 * @param File $file
374 *
375 * @return string
376 */
377 protected function getWhereClauseForFile(File $file)
378 {
379 if ((int)$file->_getPropertyRaw('uid') > 0) {
380 $where = 'uid=' . (int)$file->getUid();
381 } else {
382 $where = sprintf(
383 'storage=%u AND identifier LIKE %s',
384 (int)$file->getStorage()->getUid(),
385 $this->getDatabaseConnection()->fullQuoteStr($file->_getPropertyRaw('identifier'), $this->table)
386 );
387 }
388 return $where;
389 }
390
391 /**
392 * Remove a sys_file record from the database
393 *
394 * @param int $fileUid
395 * @return void
396 */
397 public function remove($fileUid)
398 {
399 $this->getDatabaseConnection()->exec_DELETEquery($this->table, 'uid=' . (int)$fileUid);
400 $this->updateRefIndex($fileUid);
401 $this->emitRecordDeletedSignal($fileUid);
402 }
403
404 /**
405 * Update Reference Index (sys_refindex) for a file
406 *
407 * @param int $id Record UID
408 * @return void
409 */
410 public function updateRefIndex($id)
411 {
412 /** @var $refIndexObj ReferenceIndex */
413 $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
414 $refIndexObj->updateRefIndexTable($this->table, $id);
415 }
416
417 /*
418 * Get the SignalSlot dispatcher
419 *
420 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
421 */
422 protected function getSignalSlotDispatcher()
423 {
424 return $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
425 }
426
427 /**
428 * Get the ObjectManager
429 *
430 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
431 */
432 protected function getObjectManager()
433 {
434 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
435 }
436
437 /**
438 * Signal that is called after an IndexRecord is updated
439 *
440 * @param array $data
441 * @signal
442 */
443 protected function emitRecordUpdatedSignal(array $data)
444 {
445 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\Index\FileIndexRepository::class, 'recordUpdated', array($data));
446 }
447
448 /**
449 * Signal that is called after an IndexRecord is created
450 *
451 * @param array $data
452 * @signal
453 */
454 protected function emitRecordCreatedSignal(array $data)
455 {
456 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\Index\FileIndexRepository::class, 'recordCreated', array($data));
457 }
458
459 /**
460 * Signal that is called after an IndexRecord is deleted
461 *
462 * @param int $fileUid
463 * @signal
464 */
465 protected function emitRecordDeletedSignal($fileUid)
466 {
467 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\Index\FileIndexRepository::class, 'recordDeleted', array($fileUid));
468 }
469
470 /**
471 * Signal that is called after an IndexRecord is marked as missing
472 *
473 * @param int $fileUid
474 * @signal
475 */
476 protected function emitRecordMarkedAsMissingSignal($fileUid)
477 {
478 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Resource\Index\FileIndexRepository::class, 'recordMarkedAsMissing', array($fileUid));
479 }
480 }