Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
35 / 35 |
|
100.00% |
6 / 6 |
CRAP | |
100.00% |
1 / 1 |
MediaModerationFileProcessor | |
100.00% |
35 / 35 |
|
100.00% |
6 / 6 |
14 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
fileHasAllowedMediaType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
fileHasAllowedMimeType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
fileCanBeRendered | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
5 | |||
canScanFile | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
insertFile | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\MediaModeration\Services; |
4 | |
5 | use ArchivedFile; |
6 | use Error; |
7 | use File; |
8 | use MediaHandlerFactory; |
9 | use Psr\Log\LoggerInterface; |
10 | |
11 | class MediaModerationFileProcessor { |
12 | /** @var string[] An array of mime types that are supported by PhotoDNA. */ |
13 | public const ALLOWED_MIME_TYPES = [ |
14 | 'image/gif', |
15 | 'image/jpeg', |
16 | 'image/png', |
17 | 'image/bmp', |
18 | 'image/tiff', |
19 | ]; |
20 | |
21 | /** @var array An array of mediatypes that can be properly converted to an accepted mime type for PhotoDNA. */ |
22 | private const ALLOWED_MEDIA_TYPES = [ |
23 | MEDIATYPE_BITMAP, |
24 | MEDIATYPE_DRAWING, |
25 | ]; |
26 | |
27 | private MediaModerationDatabaseManager $mediaModerationDatabaseManager; |
28 | private MediaHandlerFactory $mediaHandlerFactory; |
29 | private LoggerInterface $logger; |
30 | |
31 | public function __construct( |
32 | MediaModerationDatabaseManager $mediaModerationDatabaseManager, |
33 | MediaHandlerFactory $mediaHandlerFactory, |
34 | LoggerInterface $logger |
35 | ) { |
36 | $this->mediaModerationDatabaseManager = $mediaModerationDatabaseManager; |
37 | $this->mediaHandlerFactory = $mediaHandlerFactory; |
38 | $this->logger = $logger; |
39 | } |
40 | |
41 | /** |
42 | * Returns whether a file has an allowed media type. |
43 | * This check is needed because some files may be |
44 | * renderable but not in a supported format (T352234). |
45 | * |
46 | * @param File|ArchivedFile $file |
47 | * @return bool |
48 | */ |
49 | private function fileHasAllowedMediaType( $file ): bool { |
50 | return in_array( $file->getMediaType(), self::ALLOWED_MEDIA_TYPES, true ); |
51 | } |
52 | |
53 | /** |
54 | * Returns whether a file has an allowed mime type |
55 | * and therefore could be sent directly to PhotoDNA |
56 | * without having to convert the file type. |
57 | * |
58 | * @param File|ArchivedFile $file |
59 | * @return bool |
60 | */ |
61 | private function fileHasAllowedMimeType( $file ): bool { |
62 | return in_array( $file->getMimeType(), self::ALLOWED_MIME_TYPES, true ); |
63 | } |
64 | |
65 | /** |
66 | * Returns whether a file can be likely rendered, |
67 | * which is the result of File::canRender. The behaviour |
68 | * is similar for ArchivedFile objects. |
69 | * |
70 | * @param File|ArchivedFile $file |
71 | * @return bool |
72 | */ |
73 | private function fileCanBeRendered( $file ): bool { |
74 | if ( $file instanceof File ) { |
75 | return $file->canRender(); |
76 | } |
77 | $mediaHandler = $this->mediaHandlerFactory->getHandler( $file->getMimeType() ); |
78 | if ( $mediaHandler ) { |
79 | try { |
80 | // This suppression and passing of ArchivedFile to a MediaHandler method |
81 | // which expects a File object is in the same way as ArchivedFile::pageCount. |
82 | // TODO: Fix me if ArchivedFile ever extends File. |
83 | // @phan-suppress-next-line PhanTypeMismatchArgument |
84 | $fileCanBeRendered = $mediaHandler->canRender( $file ); |
85 | } catch ( Error $e ) { |
86 | // All errors need to be caught, because a method not existing |
87 | // will raise the generic in-built Error exception. |
88 | // If the MediaHandler raises an exception for any reason the |
89 | // result of this method will be false, and no further actions |
90 | // would be taken for this file. |
91 | $this->logger->error( |
92 | 'Call to MediaHandler::canRender with an ArchivedFile did not work ' . |
93 | 'for handler {handlerclass}', |
94 | [ |
95 | 'handlerclass' => get_class( $mediaHandler ), |
96 | 'exception' => $e |
97 | ] |
98 | ); |
99 | return false; |
100 | } |
101 | // The ArchivedFile::exists check is done to make this similar to File::canRender. |
102 | return $fileCanBeRendered && $file->exists(); |
103 | } |
104 | return false; |
105 | } |
106 | |
107 | /** |
108 | * Returns whether a file can be scanned by PhotoDNA. |
109 | * |
110 | * This currently is limited to whether the file has |
111 | * a MIME type that is supported or can be rendered |
112 | * into a thumbnail of a supported MIME type. |
113 | * |
114 | * This is to be used to determine whether an image |
115 | * should be added to the scan table and should be |
116 | * used before attempting to scan the file. |
117 | * |
118 | * @param File|ArchivedFile $file |
119 | * @return bool |
120 | */ |
121 | public function canScanFile( $file ): bool { |
122 | $canScanFile = $this->fileHasAllowedMediaType( $file ) && |
123 | ( |
124 | $this->fileHasAllowedMimeType( $file ) || |
125 | $this->fileCanBeRendered( $file ) |
126 | ); |
127 | if ( !$canScanFile ) { |
128 | $this->logger->debug( |
129 | 'File with SHA-1 {sha1} cannot be scanned by PhotoDNA', |
130 | [ 'sha1' => $file->getSha1() ] |
131 | ); |
132 | } |
133 | return $canScanFile; |
134 | } |
135 | |
136 | /** |
137 | * Should be called when a file has been created in the |
138 | * 'image' table, or when backfilling entries from the |
139 | * image, oldimage, and filearchive tables. |
140 | * |
141 | * @param File|ArchivedFile $file |
142 | * @return void |
143 | */ |
144 | public function insertFile( $file ): void { |
145 | if ( $this->canScanFile( $file ) ) { |
146 | $this->mediaModerationDatabaseManager->insertFileToScanTable( $file ); |
147 | } |
148 | } |
149 | } |