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