1715656bda8447b6e933ef39378f1c81d8257a9d
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Index / FileIndexRepository.php
1 <?php
2
3 namespace TYPO3\CMS\Core\Resource\Index;
4
5 /***************************************************************
6 * Copyright notice
7 *
8 * (c) 2013 Steffen Ritter <steffen.ritter@typo3.org>
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the text file GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30
31 use TYPO3\CMS\Core\SingletonInterface;
32 use TYPO3\CMS\Core\Utility\GeneralUtility;
33 use TYPO3\CMS\Core\Resource\File;
34
35 /**
36 * Repository Class as an abstraction layer to sys_file
37 *
38 * Every access to table sys_file_metadata which is not handled by TCEmain
39 * has to use this Repository class.
40 *
41 * This is meant for FAL internal use only!.
42 */
43 class FileIndexRepository implements SingletonInterface {
44
45 /**
46 * @var string
47 */
48 protected $table = 'sys_file';
49
50 /**
51 * A list of properties which are to be persisted
52 *
53 * @var array
54 */
55 protected $fields = array(
56 'uid', 'pid', 'missing', 'type', 'storage', 'identifier', 'identifier_hash', 'extension',
57 'mime_type', 'name', 'sha1', 'size', 'creation_date', 'modification_date', 'folder_hash'
58 );
59
60 /**
61 * Gets database instance
62 *
63 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
64 */
65 protected function getDatabaseConnection() {
66 return $GLOBALS['TYPO3_DB'];
67 }
68
69 /**
70 * Gets the Resource Factory
71 *
72 * @return \TYPO3\CMS\Core\Resource\ResourceFactory
73 */
74 protected function getResourceFactory() {
75 return \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
76 }
77
78
79 /**
80 * Returns an Instance of the Repository
81 *
82 * @return FileIndexRepository
83 */
84 public static function getInstance() {
85 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Index\\FileIndexRepository');
86 }
87
88 /**
89 * Retrieves Index record for a given $combinedIdentifier
90 *
91 * @param string $combinedIdentifier
92 * @return array|boolean
93 */
94 public function findOneByCombinedIdentifier($combinedIdentifier) {
95 list($storageUid, $identifier) = GeneralUtility::trimExplode(':', $combinedIdentifier, FALSE, 2);
96 return $this->findOneByStorageUidAndIdentifier($storageUid, $identifier);
97 }
98
99 /**
100 * Retrieves Index record for a given $fileUid
101 *
102 * @param int $fileUid
103 * @return array|boolean
104 */
105 public function findOneByUid($fileUid) {
106 $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
107 implode(',', $this->fields),
108 $this->table,
109 'uid=' . (int)$fileUid
110 );
111 return is_array($row) ? $row : FALSE;
112 }
113
114 /**
115 * Retrieves Index record for a given $storageUid and $identifier
116 *
117 * @param int $storageUid
118 * @param string $identifier
119 * @return array|boolean
120 *
121 * @internal only for use from FileRepository
122 */
123 public function findOneByStorageUidAndIdentifier($storageUid, $identifier) {
124 $identifierHash = $this->getResourceFactory()->getStorageObject($storageUid)->hashFileIdentifier($identifier);
125 return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
126 }
127
128 /**
129 * Retrieves Index record for a given $storageUid and $identifier
130 *
131 * @param integer $storageUid
132 * @param string $identifierHash
133 * @return array|boolean
134 *
135 * @internal only for use from FileRepository
136 */
137 public function findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash) {
138 $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
139 implode(',', $this->fields),
140 $this->table,
141 sprintf('storage=%u AND identifier_hash=%s', (int)$storageUid, $this->getDatabaseConnection()->fullQuoteStr($identifierHash, $this->table))
142 );
143 return is_array($row) ? $row : FALSE;
144 }
145
146 /**
147 * Retrieves Index record for a given $fileObject
148 *
149 * @param \TYPO3\CMS\Core\Resource\FileInterface $fileObject
150 * @return array|boolean
151 *
152 * @internal only for use from FileRepository
153 */
154 public function findOneByFileObject(\TYPO3\CMS\Core\Resource\FileInterface $fileObject) {
155 $storageUid = $fileObject->getStorage()->getUid();
156 $identifierHash = $fileObject->getHashedIdentifier();
157 return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
158 }
159
160 /**
161 * Returns all indexed files which match the content hash
162 * Used by the indexer to detect already present files
163 *
164 * @param string $hash
165 * @return mixed
166 */
167 public function findByContentHash($hash) {
168 if (!preg_match('/^[0-9a-f]{40}$/i', $hash)) {
169 return array();
170 }
171 $resultRows = $this->getDatabaseConnection()->exec_SELECTgetRows(
172 implode(',', $this->fields),
173 $this->table,
174 'sha1=' . $this->getDatabaseConnection()->fullQuoteStr($hash, $this->table)
175 );
176 return $resultRows;
177 }
178
179 /**
180 * Find all records for files in a Folder
181 *
182 * @param \TYPO3\CMS\Core\Resource\Folder $folder
183 * @return array|NULL
184 */
185 public function findByFolder(\TYPO3\CMS\Core\Resource\Folder $folder) {
186 $resultRows = $this->getDatabaseConnection()->exec_SELECTgetRows(
187 implode(',', $this->fields),
188 $this->table,
189 'folder_hash = ' . $this->getDatabaseConnection()->fullQuoteStr($folder->getHashedIdentifier(), $this->table) .
190 ' AND storage = ' . (int)$folder->getStorage()->getUid(),
191 '',
192 '',
193 '',
194 'identifier'
195 );
196 return $resultRows;
197 }
198 /**
199 * Adds a file to the index
200 *
201 * @param File $file
202 * @return void
203 */
204 public function add(File $file) {
205 if ($this->hasIndexRecord($file)) {
206 $this->update($file);
207 if ($file->_getPropertyRaw('uid') === NULL) {
208 $file->updateProperties($this->findOneByFileObject($file));
209 }
210 } else {
211 $file->updateProperties(array('uid' => $this->insertRecord($file->getProperties())));
212 }
213 }
214
215 /**
216 * Add data from record (at indexing time)
217 *
218 * @param array $data
219 * @return array
220 */
221 public function addRaw(array $data) {
222 $data['uid'] = $this->insertRecord($data);
223 return $data;
224 }
225
226 /**
227 * Helper to reduce code duplication
228 *
229 * @param array $data
230 *
231 * @return integer
232 */
233 protected function insertRecord(array $data) {
234 $data = array_intersect_key($data, array_flip($this->fields));
235 $data['tstamp'] = time();
236 $this->getDatabaseConnection()->exec_INSERTquery($this->table, $data);
237 $data['uid'] = $this->getDatabaseConnection()->sql_insert_id();
238 $this->emitRecordCreated($data);
239 return $data['uid'];
240 }
241 /**
242 * Checks if a file is indexed
243 *
244 * @param File $file
245 * @return boolean
246 */
247 public function hasIndexRecord(File $file) {
248 return $this->getDatabaseConnection()->exec_SELECTcountRows('uid', $this->table, $this->getWhereClauseForFile($file)) >= 1;
249 }
250
251 /**
252 * Updates the index record in the database
253 *
254 * @param File $file
255 * @return void
256 */
257 public function update(File $file) {
258 $updatedProperties = array_intersect($this->fields, $file->getUpdatedProperties());
259 $updateRow = array();
260 foreach ($updatedProperties as $key) {
261 $updateRow[$key] = $file->getProperty($key);
262 }
263 if (count($updateRow) > 0) {
264 $updateRow['tstamp'] = time();
265 $this->getDatabaseConnection()->exec_UPDATEquery($this->table, $this->getWhereClauseForFile($file), $updateRow);
266 $this->emitRecordUpdated(array_intersect_key($file->getProperties(), array_flip($this->fields)));
267 }
268 }
269
270 /**
271 * Finds the files needed for second indexer step
272 *
273 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
274 * @param integer $limit
275 * @return array
276 */
277 public function findInStorageWithIndexOutstanding(\TYPO3\CMS\Core\Resource\ResourceStorage $storage, $limit = -1) {
278 return $this->getDatabaseConnection()->exec_SELECTgetRows(
279 implode(',', $this->fields),
280 $this->table,
281 'tstamp > last_indexed AND storage = ' . (int)$storage->getUid(),
282 '',
283 'tstamp ASC',
284 (int)$limit > 0 ? (int)$limit : ''
285 );
286 }
287
288
289 /**
290 * Helper function for the Indexer to detect missing files
291 *
292 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
293 * @param array $uidList
294 * @return array
295 */
296 public function findInStorageAndNotInUidList(\TYPO3\CMS\Core\Resource\ResourceStorage $storage, array $uidList) {
297 array_walk($uidList, 'intval');
298 $uidList = array_unique($uidList);
299
300 return $this->getDatabaseConnection()->exec_SELECTgetRows(
301 implode(',', $this->fields),
302 $this->table,
303 'storage = ' . (int)$storage->getUid() . ' AND uid NOT IN (' . implode(',', $uidList) . ')'
304 );
305 }
306
307 /**
308 * Updates the timestamp when the file indexer extracted metadata
309 *
310 * @param integer $fileUid
311 * @return void
312 */
313 public function updateIndexingTime($fileUid) {
314 $this->getDatabaseConnection()->exec_UPDATEquery($this->table, 'uid = ' . (int)$fileUid, array('last_indexed' => time()));
315 }
316
317 /**
318 * Marks given file as missing in sys_file
319 *
320 * @param integer $fileUid
321 * @return void
322 */
323 public function markFileAsMissing($fileUid) {
324 $this->getDatabaseConnection()->exec_UPDATEquery($this->table, 'uid = ' . (int)$fileUid, array('missing' => 1));
325 }
326
327 /**
328 * Returns a where clause to find a file in database
329 *
330 * @param File $file
331 *
332 * @return string
333 */
334 protected function getWhereClauseForFile(File $file) {
335 if ((int)$file->_getPropertyRaw('uid') > 0) {
336 $where = 'uid=' . (int)$file->getUid();
337 } else {
338 $where = sprintf(
339 'storage=%u AND identifier=%s',
340 (int)$file->getStorage()->getUid(),
341 $this->getDatabaseConnection()->fullQuoteStr($file->_getPropertyRaw('identifier'), $this->table)
342 );
343 }
344 return $where;
345 }
346
347 /**
348 * Remove a sys_file record from the database
349 *
350 * @param integer $fileUid
351 * @return void
352 */
353 public function remove($fileUid) {
354 $this->getDatabaseConnection()->exec_DELETEquery($this->table, 'uid=' . (int)$fileUid);
355 $this->emitRecordDeleted($fileUid);
356 }
357
358 /*
359 * Get the SignalSlot dispatcher
360 *
361 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
362 */
363 protected function getSignalSlotDispatcher() {
364 return $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
365 }
366
367 /**
368 * Get the ObjectManager
369 *
370 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
371 */
372 protected function getObjectManager() {
373 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
374 }
375
376
377
378 /**
379 * Signal that is called after an IndexRecord is updated
380 *
381 * @param array $data
382 * @signal
383 */
384 protected function emitRecordUpdated(array $data) {
385 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Index\\FileIndexRepository', 'recordUpdated', array($data));
386 }
387
388 /**
389 * Signal that is called after an IndexRecord is created
390 *
391 * @param array $data
392 * @signal
393 */
394 protected function emitRecordCreated(array $data) {
395 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Index\\FileIndexRepository', 'recordCreated', array($data));
396 }
397
398 /**
399 * Signal that is called after an IndexRecord is deleted
400 *
401 * @param integer $fileUid
402 * @signal
403 */
404 protected function emitRecordDeleted($fileUid) {
405 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Index\\FileIndexRepository', 'recordDeleted', array($fileUid));
406 }
407 }