Completed
Push — master ( f65802...ddb531 )
by
unknown
39:08 queued 15:48
created

FileIndexRepository::updateRefIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Resource\Index;
17
18
use Psr\EventDispatcher\EventDispatcherInterface;
19
use TYPO3\CMS\Core\Database\Connection;
20
use TYPO3\CMS\Core\Database\ConnectionPool;
21
use TYPO3\CMS\Core\Database\ReferenceIndex;
22
use TYPO3\CMS\Core\Resource\Event\AfterFileAddedToIndexEvent;
23
use TYPO3\CMS\Core\Resource\Event\AfterFileMarkedAsMissingEvent;
24
use TYPO3\CMS\Core\Resource\Event\AfterFileRemovedFromIndexEvent;
25
use TYPO3\CMS\Core\Resource\Event\AfterFileUpdatedInIndexEvent;
26
use TYPO3\CMS\Core\Resource\File;
27
use TYPO3\CMS\Core\Resource\FileInterface;
28
use TYPO3\CMS\Core\Resource\Folder;
29
use TYPO3\CMS\Core\Resource\ResourceFactory;
30
use TYPO3\CMS\Core\Resource\ResourceStorage;
31
use TYPO3\CMS\Core\SingletonInterface;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
34
/**
35
 * Repository Class as an abstraction layer to sys_file
36
 *
37
 * Every access to table sys_file_metadata which is not handled by DataHandler
38
 * has to use this Repository class.
39
 *
40
 * @internal This is meant for FAL internal use only!
41
 */
42
class FileIndexRepository implements SingletonInterface
43
{
44
    /**
45
     * @var string
46
     */
47
    protected $table = 'sys_file';
48
49
    /**
50
     * @var EventDispatcherInterface
51
     */
52
    protected $eventDispatcher;
53
54
    /**
55
     * A list of properties which are to be persisted
56
     *
57
     * @var array
58
     */
59
    protected $fields = [
60
        'uid', 'pid', 'missing', 'type', 'storage', 'identifier', 'identifier_hash', 'extension',
61
        'mime_type', 'name', 'sha1', 'size', 'creation_date', 'modification_date', 'folder_hash'
62
    ];
63
64
    /**
65
     * Gets the Resource Factory
66
     *
67
     * @return ResourceFactory
68
     */
69
    protected function getResourceFactory()
70
    {
71
        return GeneralUtility::makeInstance(ResourceFactory::class);
72
    }
73
74
    /**
75
     * Returns an Instance of the Repository
76
     *
77
     * @return FileIndexRepository
78
     */
79
    public static function getInstance()
80
    {
81
        return GeneralUtility::makeInstance(self::class);
82
    }
83
84
    public function __construct(EventDispatcherInterface $eventDispatcher)
85
    {
86
        $this->eventDispatcher = $eventDispatcher;
87
    }
88
89
    /**
90
     * Retrieves Index record for a given $combinedIdentifier
91
     *
92
     * @param string $combinedIdentifier
93
     * @return array|bool
94
     */
95
    public function findOneByCombinedIdentifier($combinedIdentifier)
96
    {
97
        [$storageUid, $identifier] = GeneralUtility::trimExplode(':', $combinedIdentifier, false, 2);
98
        return $this->findOneByStorageUidAndIdentifier($storageUid, $identifier);
0 ignored issues
show
Bug introduced by
$storageUid of type string is incompatible with the type integer expected by parameter $storageUid of TYPO3\CMS\Core\Resource\...orageUidAndIdentifier(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

98
        return $this->findOneByStorageUidAndIdentifier(/** @scrutinizer ignore-type */ $storageUid, $identifier);
Loading history...
99
    }
100
101
    /**
102
     * Retrieves Index record for a given $fileUid
103
     *
104
     * @param int $fileUid
105
     * @return array|bool
106
     */
107
    public function findOneByUid($fileUid)
108
    {
109
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
110
            ->getQueryBuilderForTable($this->table);
111
112
        $row = $queryBuilder
113
            ->select(...$this->fields)
114
            ->from($this->table)
115
            ->where(
116
                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileUid, \PDO::PARAM_INT))
117
            )
118
            ->execute()
119
            ->fetch();
120
121
        return is_array($row) ? $row : false;
122
    }
123
124
    /**
125
     * Retrieves Index record for a given $storageUid and $identifier
126
     *
127
     * @param int $storageUid
128
     * @param string $identifier
129
     * @return array|bool
130
     *
131
     * @internal only for use from FileRepository
132
     */
133
    public function findOneByStorageUidAndIdentifier($storageUid, $identifier)
134
    {
135
        $identifierHash = $this->getResourceFactory()->getStorageObject($storageUid)->hashFileIdentifier($identifier);
136
        return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
137
    }
138
139
    /**
140
     * Retrieves Index record for a given $storageUid and $identifier
141
     *
142
     * @param int $storageUid
143
     * @param string $identifierHash
144
     * @return array|bool
145
     *
146
     * @internal only for use from FileRepository
147
     */
148
    public function findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash)
149
    {
150
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
151
            ->getQueryBuilderForTable($this->table);
152
153
        $row = $queryBuilder
154
            ->select(...$this->fields)
155
            ->from($this->table)
156
            ->where(
157
                $queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter($storageUid, \PDO::PARAM_INT)),
158
                $queryBuilder->expr()->eq('identifier_hash', $queryBuilder->createNamedParameter($identifierHash))
159
            )
160
            ->execute()
161
            ->fetch();
162
163
        return is_array($row) ? $row : false;
164
    }
165
166
    /**
167
     * Retrieves Index record for a given $fileObject
168
     *
169
     * @param FileInterface $fileObject
170
     * @return array|bool
171
     *
172
     * @internal only for use from FileRepository
173
     */
174
    public function findOneByFileObject(FileInterface $fileObject)
175
    {
176
        $storageUid = $fileObject->getStorage()->getUid();
177
        $identifierHash = $fileObject->getHashedIdentifier();
178
        return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
179
    }
180
181
    /**
182
     * Returns all indexed files which match the content hash
183
     * Used by the indexer to detect already present files
184
     *
185
     * @param string $hash
186
     * @return mixed
187
     */
188
    public function findByContentHash($hash)
189
    {
190
        if (!preg_match('/^[0-9a-f]{40}$/i', $hash)) {
191
            return [];
192
        }
193
194
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
195
            ->getQueryBuilderForTable($this->table);
196
197
        $resultRows = $queryBuilder
198
            ->select(...$this->fields)
199
            ->from($this->table)
200
            ->where(
201
                $queryBuilder->expr()->eq('sha1', $queryBuilder->createNamedParameter($hash, \PDO::PARAM_STR))
202
            )
203
            ->execute()
204
            ->fetchAll();
205
206
        return $resultRows;
207
    }
208
209
    /**
210
     * Find all records for files in a Folder
211
     *
212
     * @param Folder $folder
213
     * @return array|null
214
     */
215
    public function findByFolder(Folder $folder)
216
    {
217
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
218
            ->getQueryBuilderForTable($this->table);
219
220
        $result = $queryBuilder
221
            ->select(...$this->fields)
222
            ->from($this->table)
223
            ->where(
224
                $queryBuilder->expr()->eq(
225
                    'folder_hash',
226
                    $queryBuilder->createNamedParameter($folder->getHashedIdentifier(), \PDO::PARAM_STR)
227
                ),
228
                $queryBuilder->expr()->eq(
229
                    'storage',
230
                    $queryBuilder->createNamedParameter($folder->getStorage()->getUid(), \PDO::PARAM_INT)
231
                )
232
            )
233
            ->execute();
234
235
        $resultRows = [];
236
        while ($row = $result->fetch()) {
237
            $resultRows[$row['identifier']] = $row;
238
        }
239
240
        return $resultRows;
241
    }
242
243
    /**
244
     * Find all records for files in an array of Folders
245
     *
246
     * @param Folder[] $folders
247
     * @param bool $includeMissing
248
     * @param string $fileName
249
     * @return array|null
250
     */
251
    public function findByFolders(array $folders, $includeMissing = true, $fileName = null)
252
    {
253
        $storageUids = [];
254
        $folderIdentifiers = [];
255
256
        foreach ($folders as $folder) {
257
            if (!$folder instanceof Folder) {
258
                continue;
259
            }
260
261
            $storageUids[] = (int)$folder->getStorage()->getUid();
262
            $folderIdentifiers[] = $folder->getHashedIdentifier();
263
        }
264
265
        $storageUids = array_unique($storageUids);
266
        $folderIdentifiers = array_unique($folderIdentifiers);
267
268
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
269
270
        $queryBuilder
271
            ->select(...$this->fields)
272
            ->from($this->table)
273
            ->where(
274
                $queryBuilder->expr()->in(
275
                    'folder_hash',
276
                    $queryBuilder->createNamedParameter($folderIdentifiers, Connection::PARAM_STR_ARRAY)
277
                ),
278
                $queryBuilder->expr()->in(
279
                    'storage',
280
                    $queryBuilder->createNamedParameter($storageUids, Connection::PARAM_INT_ARRAY)
281
                )
282
            );
283
284
        if (isset($fileName)) {
285
            $nameParts = str_getcsv($fileName, ' ');
286
            foreach ($nameParts as $part) {
287
                $part = trim($part);
288
                if ($part !== '') {
289
                    $queryBuilder->andWhere(
290
                        $queryBuilder->expr()->like(
291
                            'name',
292
                            $queryBuilder->createNamedParameter(
293
                                '%' . $queryBuilder->escapeLikeWildcards($part) . '%',
294
                                \PDO::PARAM_STR
295
                            )
296
                        )
297
                    );
298
                }
299
            }
300
        }
301
302
        if (!$includeMissing) {
303
            $queryBuilder->andWhere($queryBuilder->expr()->eq('missing', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)));
304
        }
305
306
        $result = $queryBuilder->execute();
307
308
        $fileRecords = [];
309
        while ($fileRecord = $result->fetch()) {
310
            $fileRecords[$fileRecord['identifier']] = $fileRecord;
311
        }
312
313
        return $fileRecords;
314
    }
315
316
    /**
317
     * Adds a file to the index
318
     *
319
     * @param File $file
320
     */
321
    public function add(File $file)
322
    {
323
        if ($this->hasIndexRecord($file)) {
324
            $this->update($file);
325
            if ($file->_getPropertyRaw('uid') === null) {
326
                $file->updateProperties($this->findOneByFileObject($file));
0 ignored issues
show
Bug introduced by
It seems like $this->findOneByFileObject($file) can also be of type false; however, parameter $properties of TYPO3\CMS\Core\Resource\File::updateProperties() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

326
                $file->updateProperties(/** @scrutinizer ignore-type */ $this->findOneByFileObject($file));
Loading history...
327
            }
328
        } else {
329
            $file->updateProperties(['uid' => $this->insertRecord($file->getProperties())]);
330
        }
331
    }
332
333
    /**
334
     * Add data from record (at indexing time)
335
     *
336
     * @param array $data
337
     * @return array
338
     */
339
    public function addRaw(array $data)
340
    {
341
        $data['uid'] = $this->insertRecord($data);
342
        return $data;
343
    }
344
345
    /**
346
     * Helper to reduce code duplication
347
     *
348
     * @param array $data
349
     *
350
     * @return int
351
     */
352
    protected function insertRecord(array $data)
353
    {
354
        $data = array_intersect_key($data, array_flip($this->fields));
355
        $data['tstamp'] = time();
356
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
357
        $connection->insert(
358
            $this->table,
359
            $data
360
        );
361
        $data['uid'] = $connection->lastInsertId($this->table);
362
        $this->updateRefIndex($data['uid']);
363
        $this->eventDispatcher->dispatch(new AfterFileAddedToIndexEvent($data['uid'], $data));
364
        return $data['uid'];
365
    }
366
367
    /**
368
     * Checks if a file is indexed
369
     *
370
     * @param File $file
371
     * @return bool
372
     */
373
    public function hasIndexRecord(File $file)
374
    {
375
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
376
377
        if ((int)$file->_getPropertyRaw('uid') > 0) {
378
            $constraints = [
379
                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($file->getUid(), \PDO::PARAM_INT))
380
            ];
381
        } else {
382
            $constraints = [
383
                $queryBuilder->expr()->eq(
384
                    'storage',
385
                    $queryBuilder->createNamedParameter($file->getStorage()->getUid(), \PDO::PARAM_INT)
386
                ),
387
                $queryBuilder->expr()->eq(
388
                    'identifier',
389
                    $queryBuilder->createNamedParameter($file->_getPropertyRaw('identifier'), \PDO::PARAM_STR)
390
                )
391
            ];
392
        }
393
394
        $count = $queryBuilder
395
            ->count('uid')
396
            ->from($this->table)
397
            ->where(...$constraints)
398
            ->execute()
399
            ->fetchColumn(0);
400
401
        return (bool)$count;
402
    }
403
404
    /**
405
     * Updates the index record in the database
406
     *
407
     * @param File $file
408
     */
409
    public function update(File $file)
410
    {
411
        $updatedProperties = array_intersect($this->fields, $file->getUpdatedProperties());
412
        $updateRow = [];
413
        foreach ($updatedProperties as $key) {
414
            $updateRow[$key] = $file->getProperty($key);
415
        }
416
        if (!empty($updateRow)) {
417
            if ((int)$file->_getPropertyRaw('uid') > 0) {
418
                $constraints = ['uid' => (int)$file->getUid()];
419
            } else {
420
                $constraints = [
421
                    'storage' => (int)$file->getStorage()->getUid(),
422
                    'identifier' => $file->_getPropertyRaw('identifier')
423
                ];
424
            }
425
426
            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
427
            $updateRow['tstamp'] = time();
428
429
            $connection->update(
430
                $this->table,
431
                $updateRow,
432
                $constraints
433
            );
434
435
            $this->updateRefIndex($file->getUid());
436
            $this->eventDispatcher->dispatch(new AfterFileUpdatedInIndexEvent($file, array_intersect_key($file->getProperties(), array_flip($this->fields)), $updateRow));
437
        }
438
    }
439
440
    /**
441
     * Finds the files needed for second indexer step
442
     *
443
     * @param ResourceStorage $storage
444
     * @param int $limit
445
     * @return array
446
     */
447
    public function findInStorageWithIndexOutstanding(ResourceStorage $storage, $limit = -1)
448
    {
449
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
450
451
        if ((int)$limit > 0) {
452
            $queryBuilder->setMaxResults((int)$limit);
453
        }
454
455
        $rows = $queryBuilder
456
            ->select(...$this->fields)
457
            ->from($this->table)
458
            ->where(
459
                $queryBuilder->expr()->gt('tstamp', $queryBuilder->quoteIdentifier('last_indexed')),
460
                $queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter($storage->getUid(), \PDO::PARAM_INT))
461
            )
462
            ->orderBy('tstamp', 'ASC')
463
            ->execute()
464
            ->fetchAll();
465
466
        return $rows;
467
    }
468
469
    /**
470
     * Helper function for the Indexer to detect missing files
471
     *
472
     * @param ResourceStorage $storage
473
     * @param int[] $uidList
474
     * @return array
475
     */
476
    public function findInStorageAndNotInUidList(ResourceStorage $storage, array $uidList)
477
    {
478
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
479
480
        $queryBuilder
481
            ->select(...$this->fields)
482
            ->from($this->table)
483
            ->where(
484
                $queryBuilder->expr()->eq(
485
                    'storage',
486
                    $queryBuilder->createNamedParameter($storage->getUid(), \PDO::PARAM_INT)
487
                )
488
            );
489
490
        if (!empty($uidList)) {
491
            $queryBuilder->andWhere(
492
                $queryBuilder->expr()->notIn(
493
                    'uid',
494
                    array_map('intval', $uidList)
495
                )
496
            );
497
        }
498
499
        $rows = $queryBuilder->execute()->fetchAll();
500
501
        return $rows;
502
    }
503
504
    /**
505
     * Updates the timestamp when the file indexer extracted metadata
506
     *
507
     * @param int $fileUid
508
     */
509
    public function updateIndexingTime($fileUid)
510
    {
511
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
512
        $connection->update(
513
            $this->table,
514
            [
515
                'last_indexed' => time()
516
            ],
517
            [
518
                'uid' => (int)$fileUid
519
            ]
520
        );
521
    }
522
523
    /**
524
     * Marks given file as missing in sys_file
525
     *
526
     * @param int $fileUid
527
     */
528
    public function markFileAsMissing($fileUid)
529
    {
530
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
531
        $connection->update(
532
            $this->table,
533
            [
534
                'missing' => 1
535
            ],
536
            [
537
                'uid' => (int)$fileUid
538
            ]
539
        );
540
        $this->eventDispatcher->dispatch(new AfterFileMarkedAsMissingEvent((int)$fileUid));
541
    }
542
543
    /**
544
     * Remove a sys_file record from the database
545
     *
546
     * @param int $fileUid
547
     */
548
    public function remove($fileUid)
549
    {
550
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
551
        $connection->delete(
552
            $this->table,
553
            [
554
                'uid' => (int)$fileUid
555
            ]
556
        );
557
        $this->updateRefIndex($fileUid);
558
        $this->eventDispatcher->dispatch(new AfterFileRemovedFromIndexEvent((int)$fileUid));
559
    }
560
561
    /**
562
     * Update Reference Index (sys_refindex) for a file
563
     *
564
     * @param int $id Record UID
565
     */
566
    public function updateRefIndex($id)
567
    {
568
        /** @var ReferenceIndex $refIndexObj */
569
        $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
570
        $refIndexObj->enableRuntimeCache();
571
        $refIndexObj->updateRefIndexTable($this->table, $id);
572
    }
573
}
574