Completed
Push — master ( 7a0b3c...09a41b )
by
unknown
19:44
created

FileIndexRepository::findOneByCombinedIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
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\ResourceStorage;
30
use TYPO3\CMS\Core\SingletonInterface;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33
/**
34
 * Repository Class as an abstraction layer to sys_file
35
 *
36
 * Every access to table sys_file_metadata which is not handled by DataHandler
37
 * has to use this Repository class.
38
 *
39
 * @internal This is meant for FAL internal use only!
40
 */
41
class FileIndexRepository implements SingletonInterface
42
{
43
    /**
44
     * @var string
45
     */
46
    protected $table = 'sys_file';
47
48
    /**
49
     * @var EventDispatcherInterface
50
     */
51
    protected $eventDispatcher;
52
53
    /**
54
     * A list of properties which are to be persisted
55
     *
56
     * @var array
57
     */
58
    protected $fields = [
59
        'uid', 'pid', 'missing', 'type', 'storage', 'identifier', 'identifier_hash', 'extension',
60
        'mime_type', 'name', 'sha1', 'size', 'creation_date', 'modification_date', 'folder_hash'
61
    ];
62
63
    /**
64
     * Returns an Instance of the Repository
65
     *
66
     * @return FileIndexRepository
67
     */
68
    public static function getInstance()
69
    {
70
        return GeneralUtility::makeInstance(self::class);
71
    }
72
73
    public function __construct(EventDispatcherInterface $eventDispatcher)
74
    {
75
        $this->eventDispatcher = $eventDispatcher;
76
    }
77
78
    /**
79
     * Retrieves Index record for a given $fileUid
80
     *
81
     * @param int $fileUid
82
     * @return array|bool
83
     */
84
    public function findOneByUid($fileUid)
85
    {
86
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
87
            ->getQueryBuilderForTable($this->table);
88
89
        $row = $queryBuilder
90
            ->select(...$this->fields)
91
            ->from($this->table)
92
            ->where(
93
                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileUid, \PDO::PARAM_INT))
94
            )
95
            ->execute()
96
            ->fetch();
97
98
        return is_array($row) ? $row : false;
99
    }
100
101
    /**
102
     * Retrieves Index record for a given $storageUid and $identifier
103
     *
104
     * @param int $storageUid
105
     * @param string $identifierHash
106
     * @return array|bool
107
     *
108
     * @internal only for use from FileRepository
109
     */
110
    public function findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash)
111
    {
112
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
113
            ->getQueryBuilderForTable($this->table);
114
115
        $row = $queryBuilder
116
            ->select(...$this->fields)
117
            ->from($this->table)
118
            ->where(
119
                $queryBuilder->expr()->eq('storage', $queryBuilder->createNamedParameter($storageUid, \PDO::PARAM_INT)),
120
                $queryBuilder->expr()->eq('identifier_hash', $queryBuilder->createNamedParameter($identifierHash))
121
            )
122
            ->execute()
123
            ->fetch();
124
125
        return is_array($row) ? $row : false;
126
    }
127
128
    /**
129
     * Retrieves Index record for a given $storageUid and $identifier
130
     *
131
     * @param ResourceStorage $storage
132
     * @param string $identifier
133
     * @return array|bool
134
     *
135
     * @internal only for use from FileRepository
136
     */
137
    public function findOneByStorageAndIdentifier(ResourceStorage $storage, $identifier)
138
    {
139
        $identifierHash = $storage->hashFileIdentifier($identifier);
140
        return $this->findOneByStorageUidAndIdentifierHash($storage->getUid(), $identifierHash);
141
    }
142
143
    /**
144
     * Retrieves Index record for a given $fileObject
145
     *
146
     * @param FileInterface $fileObject
147
     * @return array|bool
148
     *
149
     * @internal only for use from FileRepository
150
     */
151
    public function findOneByFileObject(FileInterface $fileObject)
152
    {
153
        return $this->findOneByStorageAndIdentifier($fileObject->getStorage(), $fileObject->getIdentifier());
154
    }
155
156
    /**
157
     * Returns all indexed files which match the content hash
158
     * Used by the indexer to detect already present files
159
     *
160
     * @param string $hash
161
     * @return mixed
162
     */
163
    public function findByContentHash($hash)
164
    {
165
        if (!preg_match('/^[0-9a-f]{40}$/i', $hash)) {
166
            return [];
167
        }
168
169
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
170
            ->getQueryBuilderForTable($this->table);
171
172
        $resultRows = $queryBuilder
173
            ->select(...$this->fields)
174
            ->from($this->table)
175
            ->where(
176
                $queryBuilder->expr()->eq('sha1', $queryBuilder->createNamedParameter($hash, \PDO::PARAM_STR))
177
            )
178
            ->execute()
179
            ->fetchAll();
180
181
        return $resultRows;
182
    }
183
184
    /**
185
     * Find all records for files in a Folder
186
     *
187
     * @param Folder $folder
188
     * @return array|null
189
     */
190
    public function findByFolder(Folder $folder)
191
    {
192
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
193
            ->getQueryBuilderForTable($this->table);
194
195
        $result = $queryBuilder
196
            ->select(...$this->fields)
197
            ->from($this->table)
198
            ->where(
199
                $queryBuilder->expr()->eq(
200
                    'folder_hash',
201
                    $queryBuilder->createNamedParameter($folder->getHashedIdentifier(), \PDO::PARAM_STR)
202
                ),
203
                $queryBuilder->expr()->eq(
204
                    'storage',
205
                    $queryBuilder->createNamedParameter($folder->getStorage()->getUid(), \PDO::PARAM_INT)
206
                )
207
            )
208
            ->execute();
209
210
        $resultRows = [];
211
        while ($row = $result->fetch()) {
212
            $resultRows[$row['identifier']] = $row;
213
        }
214
215
        return $resultRows;
216
    }
217
218
    /**
219
     * Find all records for files in an array of Folders
220
     *
221
     * @param Folder[] $folders
222
     * @param bool $includeMissing
223
     * @param string $fileName
224
     * @return array|null
225
     */
226
    public function findByFolders(array $folders, $includeMissing = true, $fileName = null)
227
    {
228
        $storageUids = [];
229
        $folderIdentifiers = [];
230
231
        foreach ($folders as $folder) {
232
            if (!$folder instanceof Folder) {
233
                continue;
234
            }
235
236
            $storageUids[] = (int)$folder->getStorage()->getUid();
237
            $folderIdentifiers[] = $folder->getHashedIdentifier();
238
        }
239
240
        $storageUids = array_unique($storageUids);
241
        $folderIdentifiers = array_unique($folderIdentifiers);
242
243
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
244
245
        $queryBuilder
246
            ->select(...$this->fields)
247
            ->from($this->table)
248
            ->where(
249
                $queryBuilder->expr()->in(
250
                    'folder_hash',
251
                    $queryBuilder->createNamedParameter($folderIdentifiers, Connection::PARAM_STR_ARRAY)
252
                ),
253
                $queryBuilder->expr()->in(
254
                    'storage',
255
                    $queryBuilder->createNamedParameter($storageUids, Connection::PARAM_INT_ARRAY)
256
                )
257
            );
258
259
        if (isset($fileName)) {
260
            $nameParts = str_getcsv($fileName, ' ');
261
            foreach ($nameParts as $part) {
262
                $part = trim($part);
263
                if ($part !== '') {
264
                    $queryBuilder->andWhere(
265
                        $queryBuilder->expr()->like(
266
                            'name',
267
                            $queryBuilder->createNamedParameter(
268
                                '%' . $queryBuilder->escapeLikeWildcards($part) . '%',
269
                                \PDO::PARAM_STR
270
                            )
271
                        )
272
                    );
273
                }
274
            }
275
        }
276
277
        if (!$includeMissing) {
278
            $queryBuilder->andWhere($queryBuilder->expr()->eq('missing', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)));
279
        }
280
281
        $result = $queryBuilder->execute();
282
283
        $fileRecords = [];
284
        while ($fileRecord = $result->fetch()) {
285
            $fileRecords[$fileRecord['identifier']] = $fileRecord;
286
        }
287
288
        return $fileRecords;
289
    }
290
291
    /**
292
     * Adds a file to the index
293
     *
294
     * @param File $file
295
     */
296
    public function add(File $file)
297
    {
298
        if ($this->hasIndexRecord($file)) {
299
            $this->update($file);
300
            if ($file->_getPropertyRaw('uid') === null) {
301
                $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

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