[BUGFIX] Adapt IndexerService to new table structures
[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 $fileObject->setIndexingInProgess(TRUE);
93 // Get the file information of this object
94 $fileInfo = $this->gatherFileInformation($fileObject);
95 // Signal slot BEFORE the file was indexed
96 $this->emitPreFileIndexSignal($fileObject, $fileInfo);
97
98 // If the file is already indexed, then the file information will
99 // be updated on the existing record
100 if ($fileObject->isIndexed()) {
101 $fileInfo['missing'] = 0;
102 $fileObject->updateProperties($fileInfo);
103 $this->getFileIndexRepository()->update($fileObject);
104 } else {
105 // Check if a file has been moved outside of FAL -- we have some
106 // orphaned index record in this case we could update
107 $resultRows = $this->getFileIndexRepository()->findByContentHash($fileInfo['sha1']);
108 $otherFiles = array();
109 foreach ($resultRows as $row) {
110 $otherFiles[] = $this->getFactory()->getFileObject($row['uid'], $row);
111 }
112
113 $movedFile = FALSE;
114 /** @var $otherFile File */
115 foreach ($otherFiles as $otherFile) {
116 if (!$otherFile->exists()) {
117 // @todo: create a log entry
118 $movedFile = TRUE;
119 $fileInfo['missing'] = 0;
120 $otherFile->updateProperties($fileInfo);
121 $this->getFileIndexRepository()->update($otherFile);
122 $fileInfo['uid'] = $otherFile->getUid();
123 $fileObject = $otherFile;
124 // Skip the rest of the files here as we might have more files that are missing, but we can only
125 // have one entry. The optimal solution would be to merge these records then, but this requires
126 // some more advanced logic that we currently have not implemented.
127 break;
128 }
129 }
130 // File was not moved, so it is a new index record
131 if ($movedFile === FALSE) {
132 // Crdate and tstamp should not be present when updating
133 // the file object, as they only relate to the index record
134 $additionalInfo = array(
135 'crdate' => $GLOBALS['EXEC_TIME'],
136 'tstamp' => $GLOBALS['EXEC_TIME']
137 );
138 if (TYPO3_MODE === 'BE') {
139 $additionalInfo['cruser_id'] = intval($GLOBALS['BE_USER']->user['uid']);
140 }
141 $indexRecord = array_merge($fileInfo, $additionalInfo);
142
143 $fileObject->updateProperties($indexRecord);
144 $this->getFileIndexRepository()->add($fileObject);
145 }
146 }
147 // Check for an error during the execution and throw an exception
148 $error = $GLOBALS['TYPO3_DB']->sql_error();
149 if ($error) {
150 throw new \RuntimeException('Error during file indexing: "' . $error . '"', 1314455642);
151 }
152 if ($fileInfo['type'] == $fileObject::FILETYPE_IMAGE) {
153 $rawFileLocation = $fileObject->getForLocalProcessing(FALSE);
154 $metaData = array();
155 list($metaData['width'], $metaData['height']) = getimagesize($rawFileLocation);
156 $this->getMetaDataRepository()->update($fileObject->getUid(), $metaData);
157 }
158 // Signal slot AFTER the file was indexed
159 $this->emitPostFileIndexSignal($fileObject, $fileInfo);
160 $fileObject->setIndexingInProgess(FALSE);
161 if ($updateObject) {
162 return $fileObject;
163 } else {
164 return $fileInfo;
165 }
166 }
167
168 /**
169 * Indexes an array of file objects
170 * currently this is done in a simple way, however could be changed to be more performant
171 *
172 * @param File[] $fileObjects
173 * @return void
174 */
175 public function indexFiles(array $fileObjects) {
176 // emit signal
177 $this->emitPreMultipleFilesIndexSignal($fileObjects);
178 foreach ($fileObjects as $fileObject) {
179 $this->indexFile($fileObject);
180 }
181 // emit signal
182 $this->emitPostMultipleFilesIndexSignal($fileObjects);
183 }
184
185 /**
186 * Indexes all files in a given storage folder.
187 * currently this is done in a simple way, however could be changed to be more performant
188 *
189 * @param \TYPO3\CMS\Core\Resource\Folder $folder
190 * @return integer The number of indexed files.
191 */
192 public function indexFilesInFolder(\TYPO3\CMS\Core\Resource\Folder $folder) {
193 $numberOfIndexedFiles = 0;
194 $fileIdentifiers = array();
195
196 // Index all files in this folder
197 $fileObjects = $folder->getFiles();
198
199 // emit signal
200 $this->emitPreMultipleFilesIndexSignal($fileObjects);
201 foreach ($fileObjects as $fileObject) {
202 $this->indexFile($fileObject);
203 $fileIdentifiers[] = $fileObject->getIdentifier();
204 $numberOfIndexedFiles++;
205 }
206
207 // check for deleted files (file not found during indexing are marked as missing)
208 foreach ($this->getRepository()->getFileIndexRecordsForFolder($folder) as $file) {
209 if (!in_array($file['identifier'], $fileIdentifiers)) {
210 /** @var $fileObject File */
211 $fileObject = $this->getRepository()->findByIdentifier($file['uid']);
212 $fileObject->setMissing(TRUE);
213 $this->getFileIndexRepository()->update($fileObject);
214 }
215 }
216
217 // emit signal
218 $this->emitPostMultipleFilesIndexSignal($fileObjects);
219
220 // cleanup to prevent to much memory use on big folders
221 unset($fileObjects);
222 unset($fileIdentifiers);
223
224 // Call this function recursively for each subfolder
225 $subFolders = $folder->getSubfolders();
226 foreach ($subFolders as $subFolder) {
227 $numberOfIndexedFiles += $this->indexFilesInFolder($subFolder);
228 }
229 return $numberOfIndexedFiles;
230 }
231
232 /**
233 * Fetches the information for a sys_file record
234 * based on a single file
235 * this function shouldn't be used, if someone needs to fetch the file information
236 * from a file object, should be done by getProperties etc
237 *
238 * @param File $file the file to fetch the information from
239 * @return array the file information as an array
240 */
241 protected function gatherFileInformation(File $file) {
242 $fileInfo = new \ArrayObject(array());
243 $gatherDefaultInformation = new \stdClass();
244 $gatherDefaultInformation->getDefaultFileInfo = 1;
245 // signal before the files are modified
246 $this->emitPreGatherFileInformationSignal($file, $fileInfo, $gatherDefaultInformation);
247 // the check helps you to disable the regular file fetching,
248 // so a signal could actually remotely access the service
249 if ($gatherDefaultInformation->getDefaultFileInfo) {
250 $storage = $file->getStorage();
251 // TODO: See if we can't just return info, as it contains most of the
252 // stuff we put together in array form again later here.
253 $info = $storage->getFileInfo($file);
254 $defaultFileInfo = array(
255 'creation_date' => $info['ctime'],
256 'modification_date' => $info['mtime'],
257 'size' => $info['size'],
258 'identifier' => $file->getIdentifier(),
259 'storage' => $storage->getUid(),
260 'name' => $file->getName(),
261 'sha1' => $storage->hashFile($file, 'sha1'),
262 'type' => $file->getType(),
263 'mime_type' => $file->getMimeType(),
264 'extension' => $file->getExtension()
265 );
266 $fileInfo = array_merge($defaultFileInfo, $fileInfo->getArrayCopy());
267 $fileInfo = new \ArrayObject($fileInfo);
268 }
269 // signal after the file information is fetched
270 $this->emitPostGatherFileInformationSignal($file, $fileInfo, $gatherDefaultInformation);
271 return $fileInfo->getArrayCopy();
272 }
273
274 /**
275 * Signal that is called before the file information is fetched
276 * helpful if somebody wants to preprocess the record information
277 *
278 * @param File $fileObject
279 * @param array $fileInfo
280 * @param boolean $gatherDefaultInformation
281 * @signal
282 */
283 protected function emitPreGatherFileInformationSignal(File $fileObject, $fileInfo, $gatherDefaultInformation) {
284 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'preGatherFileInformation', array($fileObject, $fileInfo, $gatherDefaultInformation));
285 }
286
287 /**
288 * Signal that is called after a file object was indexed
289 *
290 * @param File $fileObject
291 * @param array $fileInfo
292 * @param boolean $hasGatheredDefaultInformation
293 * @signal
294 */
295 protected function emitPostGatherFileInformationSignal(File $fileObject, $fileInfo, $hasGatheredDefaultInformation) {
296 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'postGatherFileInformation', array($fileObject, $fileInfo, $hasGatheredDefaultInformation));
297 }
298
299 /**
300 * Signal that is called before a bunch of file objects are indexed
301 *
302 * @param array $fileObject
303 * @signal
304 */
305 protected function emitPreMultipleFilesIndexSignal(array $fileObjectsToIndex) {
306 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'preMultipleFileIndex', array($fileObjectsToIndex));
307 }
308
309 /**
310 * Signal that is called after multiple file objects were indexed
311 *
312 * @param array $fileObjectsToIndex
313 * @signal
314 */
315 protected function emitPostMultipleFilesIndexSignal(array $fileObjectsToIndex) {
316 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'postMultipleFileIndex', array($fileObjectsToIndex));
317 }
318
319 /**
320 * Signal that is called before a file object was indexed
321 *
322 * @param File $fileObject
323 * @param array $fileInfo
324 * @signal
325 */
326 protected function emitPreFileIndexSignal(File $fileObject, $fileInfo) {
327 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'preFileIndex', array($fileObject, $fileInfo));
328 }
329
330 /**
331 * Signal that is called after a file object was indexed
332 *
333 * @param File $fileObject
334 * @param array $fileInfo
335 * @signal
336 */
337 protected function emitPostFileIndexSignal(File $fileObject, $fileInfo) {
338 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\Service\\IndexerService', 'postFileIndex', array($fileObject, $fileInfo));
339 }
340
341 /**
342 * Get the SignalSlot dispatcher
343 *
344 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
345 */
346 protected function getSignalSlotDispatcher() {
347 return $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
348 }
349
350 /**
351 * Get the ObjectManager
352 *
353 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
354 */
355 protected function getObjectManager() {
356 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
357 }
358
359 /**
360 * @return \TYPO3\CMS\Core\Resource\Index\MetaDataRepository
361 */
362 protected function getMetaDataRepository() {
363 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Index\\MetaDataRepository');
364 }
365
366 }