Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
51 / 51 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
MediaModerationFileScanner | |
100.00% |
51 / 51 |
|
100.00% |
2 / 2 |
11 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
scanSha1 | |
100.00% |
42 / 42 |
|
100.00% |
1 / 1 |
10 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\MediaModeration\Services; |
4 | |
5 | use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface; |
6 | use MediaWiki\Extension\MediaModeration\PhotoDNA\IMediaModerationPhotoDNAServiceProvider; |
7 | use MediaWiki\Extension\MediaModeration\PhotoDNA\Response; |
8 | use MediaWiki\Language\RawMessage; |
9 | use MediaWiki\Status\StatusFormatter; |
10 | use Psr\Log\LoggerInterface; |
11 | use StatusValue; |
12 | |
13 | /** |
14 | * Scans SHA-1 values in batches to reduce the effect that slow requests to |
15 | * PhotoDNA have on the overall speed of scanning. |
16 | */ |
17 | class MediaModerationFileScanner { |
18 | |
19 | private MediaModerationDatabaseManager $mediaModerationDatabaseManager; |
20 | private MediaModerationDatabaseLookup $mediaModerationDatabaseLookup; |
21 | private MediaModerationFileLookup $mediaModerationFileLookup; |
22 | private MediaModerationFileProcessor $mediaModerationFileProcessor; |
23 | private IMediaModerationPhotoDNAServiceProvider $mediaModerationPhotoDNAServiceProvider; |
24 | private MediaModerationEmailer $mediaModerationEmailer; |
25 | private StatsdDataFactoryInterface $perDbNameStatsdDataFactory; |
26 | private StatusFormatter $statusFormatter; |
27 | private LoggerInterface $logger; |
28 | |
29 | public function __construct( |
30 | MediaModerationDatabaseLookup $mediaModerationDatabaseLookup, |
31 | MediaModerationDatabaseManager $mediaModerationDatabaseManager, |
32 | MediaModerationFileLookup $mediaModerationFileLookup, |
33 | MediaModerationFileProcessor $mediaModerationFileProcessor, |
34 | IMediaModerationPhotoDNAServiceProvider $mediaModerationPhotoDNAServiceProvider, |
35 | MediaModerationEmailer $mediaModerationEmailer, |
36 | StatusFormatter $statusFormatter, |
37 | StatsdDataFactoryInterface $perDbNameStatsdDataFactory, |
38 | LoggerInterface $logger |
39 | ) { |
40 | $this->mediaModerationDatabaseLookup = $mediaModerationDatabaseLookup; |
41 | $this->mediaModerationDatabaseManager = $mediaModerationDatabaseManager; |
42 | $this->mediaModerationFileLookup = $mediaModerationFileLookup; |
43 | $this->mediaModerationFileProcessor = $mediaModerationFileProcessor; |
44 | $this->mediaModerationPhotoDNAServiceProvider = $mediaModerationPhotoDNAServiceProvider; |
45 | $this->mediaModerationEmailer = $mediaModerationEmailer; |
46 | $this->statusFormatter = $statusFormatter; |
47 | $this->perDbNameStatsdDataFactory = $perDbNameStatsdDataFactory; |
48 | $this->logger = $logger; |
49 | } |
50 | |
51 | /** |
52 | * Scans the files that have the given SHA-1 |
53 | * |
54 | * @param string $sha1 |
55 | * @return StatusValue |
56 | */ |
57 | public function scanSha1( string $sha1 ): StatusValue { |
58 | $returnStatus = new StatusValue(); |
59 | // Until a match is got from PhotoDNA, the return status should be not okay as the operation has not completed. |
60 | $returnStatus->setOK( false ); |
61 | // Get the current scan status from the DB, so that we keep the current value if |
62 | // nothing matches but still update the last checked value. |
63 | $oldMatchStatus = $this->mediaModerationDatabaseLookup->getMatchStatusForSha1( $sha1 ); |
64 | $newMatchStatus = null; |
65 | foreach ( $this->mediaModerationFileLookup->getFileObjectsForSha1( $sha1 ) as $file ) { |
66 | if ( !$this->mediaModerationFileProcessor->canScanFile( $file ) ) { |
67 | // If this $file cannot be scanned, then try the next file with this SHA-1 |
68 | // and if in verbose mode output to the console about this. |
69 | $this->perDbNameStatsdDataFactory->increment( 'MediaModeration.FileScanner.CanScanFileReturnedFalse' ); |
70 | $returnStatus->fatal( new RawMessage( |
71 | 'The file $1 cannot be scanned.', |
72 | [ $file->getName() ] |
73 | ) ); |
74 | continue; |
75 | } |
76 | // Run the check using the PhotoDNA API. |
77 | $checkResult = $this->mediaModerationPhotoDNAServiceProvider->check( $file ); |
78 | /** @var Response|null $response */ |
79 | $response = $checkResult->getValue(); |
80 | if ( $response === null || $response->getStatusCode() !== Response::STATUS_OK ) { |
81 | // Assume something is wrong with the thumbnail or source file if the request fails, |
82 | // and just try a new $file with this SHA-1. Add the information about the |
83 | // failure to the return status for tracking and logging. |
84 | $returnStatus->merge( $checkResult ); |
85 | continue; |
86 | } |
87 | $newMatchStatus = $response->isMatch(); |
88 | // Stop processing this SHA-1 as we have a result. |
89 | break; |
90 | } |
91 | // Update the match status, even if none of the $file objects could be scanned. |
92 | // If no scanning was successful, then the status will remain |
93 | $this->mediaModerationDatabaseManager->updateMatchStatusForSha1( $sha1, $newMatchStatus ?? $oldMatchStatus ); |
94 | if ( $newMatchStatus && $newMatchStatus !== $oldMatchStatus ) { |
95 | // Send an email for this SHA-1 if the match status has changed to positive. |
96 | // If the match status was already positive, then an email has already been sent. |
97 | $this->mediaModerationEmailer->sendEmailForSha1( $sha1 ); |
98 | } |
99 | if ( $newMatchStatus !== null ) { |
100 | $returnStatus->setResult( true, $newMatchStatus ); |
101 | } |
102 | if ( !$returnStatus->isOK() ) { |
103 | // Create a info if the SHA-1 could not be scanned. |
104 | $this->logger->info( |
105 | 'Unable to scan SHA-1 {sha1}. MediaModerationFileScanner::scanSha1 returned this: {return-message}', |
106 | [ |
107 | 'sha1' => $sha1, |
108 | 'return-message' => $this->statusFormatter->getMessage( $returnStatus, [ 'lang' => 'en' ] ), |
109 | ] |
110 | ); |
111 | } elseif ( !$returnStatus->isGood() ) { |
112 | // Create a debug if the SHA-1 scanning succeeded with warnings. |
113 | $this->logger->debug( |
114 | 'Scan of SHA-1 {sha1} succeeded with warnings. MediaModerationFileScanner::scanSha1 ' . |
115 | 'returned this: {return-message}', |
116 | [ |
117 | 'sha1' => $sha1, |
118 | 'return-message' => $this->statusFormatter->getMessage( $returnStatus, [ 'lang' => 'en' ] ), |
119 | ] |
120 | ); |
121 | } |
122 | return $returnStatus; |
123 | } |
124 | } |