[BUGFIX] Clean-up responsibilities of FAL classes
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Service / IndexerService.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource\Service;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2011-2013 Andreas Wolf <andreas.wolf@ikt-werk.de>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 use TYPO3\CMS\Core\Resource\File;
30 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
31 use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33 /**
34 * Indexer for the virtual file system
35 * should only be accessed through the FileRepository for now
36 *
37 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
38 */
39 class IndexerService implements \TYPO3\CMS\Core\SingletonInterface {
40
41 /**
42 * @var \TYPO3\CMS\Core\Resource\FileRepository
43 */
44 protected $repository;
45
46 /**
47 * empty constructor, nothing to do here yet
48 */
49 public function __construct() {
50
51 }
52
53 /**
54 * Internal function to retrieve the file repository,
55 * if it does not exist, an instance will be created
56 *
57 * @return \TYPO3\CMS\Core\Resource\FileRepository
58 */
59 protected function getRepository() {
60 if ($this->repository === NULL) {
61 $this->repository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\FileRepository');
62 }
63 return $this->repository;
64 }
65
66 /**
67 * @return \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
68 */
69 protected function getFileIndexRepository() {
70 return GeneralUtility::makeInstance('TYPO3\CMS\Core\Resource\Index\FileIndexRepository');
71 }
72
73
74 /**
75 * Getter function for the fileFactory
76 *
77 * @return \TYPO3\CMS\Core\Resource\ResourceFactory
78 */
79 public function getFactory() {
80 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
81 }
82
83 /**
84 * Creates or updates a file index entry from a file object.
85 *
86 * @param File $fileObject
87 * @param bool $updateObject Set this to FALSE to get the indexed values. You have to take care of updating the object yourself then!
88 * @return File|array the indexed $fileObject or an array of indexed properties.
89 * @throws \RuntimeException
90 */
91 public function indexFile(File $fileObject, $updateObject = TRUE) {
92 // Get the file information of this object
93 $fileInfo = $this->gatherFileInformation($fileObject);
94 // Signal slot BEFORE the file was indexed
95 $this->emitPreFileIndexSignal($fileObject, $fileInfo);
96 // @todo: this should be done via services in the future
97 // @todo: this should take remote services into account
98 if ($fileInfo['type'] == $fileObject::FILETYPE_IMAGE && !$fileInfo['width']) {
99 $rawFileLocation = $fileObject->getForLocalProcessing(FALSE);
100 list($fileInfo['width'], $fileInfo['height']) = getimagesize($rawFileLocation);
101 }
102 // If the file is already indexed, then the file information will
103 // be updated on the existing record
104 if ($fileObject->isIndexed()) {
105 $fileInfo['missing'] = 0;
106 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_file', sprintf('uid = %d', $fileObject->getUid()), $fileInfo);
107 } else {
108 // Check if a file has been moved outside of FAL -- we have some
109 // orphaned index record in this case we could update
110 $resultRows = $this->getFileIndexRepository()->findByContentHash($fileInfo['sha1']);
111 $otherFiles = array();
112 foreach ($resultRows as $row) {
113 $otherFiles[] = $this->getFactory()->getFileObject($row['uid'], $row);
114 }
115
116 $movedFile = FALSE;
117 /** @var $otherFile File */
118 foreach ($otherFiles as $otherFile) {
119 if (!$otherFile->exists()) {
120 // @todo: create a log entry
121 $movedFile = TRUE;
122 $fileInfo['missing'] = 0;
123 $otherFile->updateProperties($fileInfo);
124 $this->getFileIndexRepository()->update($otherFile);
125 $fileInfo['uid'] = $otherFile->getUid();
126 $fileObject = $otherFile;
127 // Skip the rest of the files here as we might have more files that are missing, but we can only
128 // have one entry. The optimal solution would be to merge these records then, but this requires
129 // some more advanced logic that we currently have not implemented.
130 break;
131 }
132 }
133 // File was not moved, so it is a new index record
134 if ($movedFile === FALSE) {
135 // Crdate and tstamp should not be present when updating
136 // the file object, as they only relate to the index record
137 $additionalInfo = array(
138 'crdate' => $GLOBALS['EXEC_TIME'],
139 'tstamp' => $GLOBALS['EXEC_TIME']
140 );
141 if (TYPO3_MODE === 'BE') {
142 $additionalInfo['cruser_id'] = intval($GLOBALS['BE_USER']->user['uid']);
143 }
144 $indexRecord = array_merge($fileInfo, $additionalInfo);
145
146 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_file', $indexRecord);
147 $fileInfo['uid'] = $GLOBALS['TYPO3_DB']->sql_insert_id();
148 }
149 }
150 // Check for an error during the execution and throw an exception
151 $error = $GLOBALS['TYPO3_DB']->sql_error();
152 if ($error) {
153 throw new \RuntimeException('Error during file indexing: "' . $error . '"', 1314455642);
154 }
155 // Signal slot AFTER the file was indexed
156 $this->emitPostFileIndexSignal($fileObject, $fileInfo);
157 if ($updateObject) {
158 $fileObject->updateProperties($fileInfo);
159 return $fileObject;
160 } else {
161 return $fileInfo;
162 }
163 }
164
165 /**
166 * Indexes an array of file objects
167 * currently this is done in a simple way, however could be changed to be more performant
168 *
169 * @param File[] $fileObjects
170 * @return void
171 */
172 public function indexFiles(array $fileObjects) {
173 // emit signal
174 $this->emitPreMultipleFilesIndexSignal($fileObjects);
175 foreach ($fileObjects as $fileObject) {
176 $this->indexFile($fileObject);
177 }
178 // emit signal
179 $this->emitPostMultipleFilesIndexSignal($fileObjects);
180 }
181
182 /**
183 * Indexes all files in a given storage folder.
184 * currently this is done in a simple way, however could be changed to be more performant
185 *
186 * @param \TYPO3\CMS\Core\Resource\Folder $folder
187 * @return integer The number of indexed files.
188 */
189 public function indexFilesInFolder(\TYPO3\CMS\Core\Resource\Folder $folder) {
190 $numberOfIndexedFiles = 0;
191 $fileIdentifiers = array();
192
193 // Index all files in this folder
194 $fileObjects = $folder->getFiles();
195
196 // emit signal
197 $this->emitPreMultipleFilesIndexSignal($fileObjects);
198 foreach ($fileObjects as $fileObject) {
199 $this->indexFile($fileObject);
200 $fileIdentifiers[] = $fileObject->getIdentifier();
201 $numberOfIndexedFiles++;
202 }
203
204 // check for deleted files (file not found during indexing are marked as missing)
205 foreach ($this->getRepository()->getFileIndexRecordsForFolder($folder) as $file) {
206 if (!in_array($file['identifier'], $fileIdentifiers)) {
207 /** @var $fileObject File */
208 $fileObject = $this->getRepository()->findByIdentifier($file['uid']);
209 $fileObject->setMissing(TRUE);
210 $this->getFileIndexRepository()->update($fileObject);
211 }
212 }
213
214 // emit signal
215 $this->emitPostMultipleFilesIndexSignal($fileObjects);
216
217 // cleanup to prevent to much memory use on big folders
218 unset($fileObjects);
219 unset($fileIdentifiers);
220
221 // Call this function recursively for each subfolder
222 $subFolders = $folder->getSubfolders();
223 foreach ($subFolders as $subFolder) {
224 $numberOfIndexedFiles += $this->indexFilesInFolder($subFolder);
225 }
226 return $numberOfIndexedFiles;
227 }
228
229 /**
230 * Fetches the information for a sys_file record
231 * based on a single file
232 * this function shouldn't be used, if someone needs to fetch the file information
233 * from a file object, should be done by getProperties etc
234 *
235 * @param File $file the file to fetch the information from
236 * @return array the file information as an array
237 */
238 protected function gatherFileInformation(File $file) {
239 $fileInfo = new \ArrayObject(array());
240 $gatherDefaultInformation = new \stdClass();
241 $gatherDefaultInformation->getDefaultFileInfo = 1;
242 // signal before the files are modified
243 $this->emitPreGatherFileInformationSignal($file, $fileInfo, $gatherDefaultInformation);
244 // the check helps you to disable the regular file fetching,
245 // so a signal could actually remotely access the service
246 if ($gatherDefaultInformation->getDefaultFileInfo) {
247 $storage = $file->getStorage();
248 // TODO: See if we can't just return info, as it contains most of the
249 // stuff we put together in array form again later here.
250 $info = $storage->getFileInfo($file);
251 $defaultFileInfo = array(
252 'creation_date' => $info['ctime'],
253 'modification_date' => $info['mtime'],
254 'size' => $info['size'],
255 'identifier' => $file->getIdentifier(),
256 'storage' => $storage->getUid(),
257 'name' => $file->getName(),
258 'sha1' => $storage->hashFile($file, 'sha1'),
259 'type' => $file->getType(),
260 'mime_type' => $file->getMimeType(),
261 'extension' => $file->getExtension()
262 );
263 $fileInfo = array_merge($defaultFileInfo, $fileInfo->getArrayCopy());
264 $fileInfo = new \ArrayObject($fileInfo);
265 }
266 // signal after the file information is fetched
267 $this->emitPostGatherFileInformationSignal($file, $fileInfo, $gatherDefaultInformation);
268 return $fileInfo->getArrayCopy();
269 }
270
271 /**
272 * Signal that is called before the file information is fetched
273 * helpful if somebody wants to preprocess the record information
274 *
275 * @param File $fileObject
276 * @param array $fileInfo
277 * @param boolean $gatherDefaultInformation
278 * @signal
279 */
280 protected function emitPreGatherFileInformationSignal(File $fileObject, $fileInfo, $gatherDefaultInformation) {
281 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'preGatherFileInformation', array($fileObject, $fileInfo, $gatherDefaultInformation));
282 }
283
284 /**
285 * Signal that is called after a file object was indexed
286 *
287 * @param File $fileObject
288 * @param array $fileInfo
289 * @param boolean $hasGatheredDefaultInformation
290 * @signal
291 */
292 protected function emitPostGatherFileInformationSignal(File $fileObject, $fileInfo, $hasGatheredDefaultInformation) {
293 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'postGatherFileInformation', array($fileObject, $fileInfo, $hasGatheredDefaultInformation));
294 }
295
296 /**
297 * Signal that is called before a bunch of file objects are indexed
298 *
299 * @param array $fileObject
300 * @signal
301 */
302 protected function emitPreMultipleFilesIndexSignal(array $fileObjectsToIndex) {
303 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'preMultipleFileIndex', array($fileObjectsToIndex));
304 }
305
306 /**
307 * Signal that is called after multiple file objects were indexed
308 *
309 * @param array $fileObjectsToIndex
310 * @signal
311 */
312 protected function emitPostMultipleFilesIndexSignal(array $fileObjectsToIndex) {
313 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'postMultipleFileIndex', array($fileObjectsToIndex));
314 }
315
316 /**
317 * Signal that is called before a file object was indexed
318 *
319 * @param File $fileObject
320 * @param array $fileInfo
321 * @signal
322 */
323 protected function emitPreFileIndexSignal(File $fileObject, $fileInfo) {
324 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'preFileIndex', array($fileObject, $fileInfo));
325 }
326
327 /**
328 * Signal that is called after a file object was indexed
329 *
330 * @param File $fileObject
331 * @param array $fileInfo
332 * @signal
333 */
334 protected function emitPostFileIndexSignal(File $fileObject, $fileInfo) {
335 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'postFileIndex', array($fileObject, $fileInfo));
336 }
337
338 /**
339 * Get the SignalSlot dispatcher
340 *
341 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
342 */
343 protected function getSignalSlotDispatcher() {
344 return $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
345 }
346
347 /**
348 * Get the ObjectManager
349 *
350 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
351 */
352 protected function getObjectManager() {
353 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
354 }
355
356 }