Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
MediaModerationDatabaseManager
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
5 / 5
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 insertFileToScanTable
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 insertToScanTableInternal
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 updateMatchStatus
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 updateMatchStatusForSha1
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Extension\MediaModeration\Services;
4
5use ArchivedFile;
6use File;
7use IDBAccessObject;
8use Wikimedia\Rdbms\IDatabase;
9use Wikimedia\Timestamp\ConvertibleTimestamp;
10
11class MediaModerationDatabaseManager {
12
13    private IDatabase $dbw;
14    private MediaModerationDatabaseLookup $mediaModerationDatabaseLookup;
15
16    public function __construct(
17        IDatabase $dbw,
18        MediaModerationDatabaseLookup $mediaModerationDatabaseLookup
19    ) {
20        $this->dbw = $dbw;
21        $this->mediaModerationDatabaseLookup = $mediaModerationDatabaseLookup;
22    }
23
24    /**
25     * Takes a File object and adds a reference to the file in the
26     * mediamoderation_scan table if the reference to the file does
27     * not already exist.
28     *
29     * @param File|ArchivedFile $file The file to be added to the mediamoderation_scan table.
30     * @return void
31     */
32    public function insertFileToScanTable( $file ) {
33        if ( !$this->mediaModerationDatabaseLookup->fileExistsInScanTable(
34            $file, IDBAccessObject::READ_LATEST
35        ) ) {
36            $this->insertToScanTableInternal( $file );
37        }
38    }
39
40    /**
41     * Inserts a given File to the mediamoderation_scan table.
42     * Does not check for the existence of the File in the scan
43     * table, so will cause a DBError if the File is already in
44     * the table.
45     *
46     * @param File|ArchivedFile $file
47     * @return void
48     */
49    private function insertToScanTableInternal( $file ) {
50        // Insert a row to the mediamoderation_scan table with the SHA-1 of the file.
51        $this->dbw->newInsertQueryBuilder()
52            ->insert( 'mediamoderation_scan' )
53            ->row( [ 'mms_sha1' => $file->getSha1() ] )
54            ->caller( __METHOD__ )
55            ->execute();
56    }
57
58    /**
59     * Updates the mediamoderation_scan row with for the given file
60     * with the match status as determined by PhotoDNA.
61     *
62     * This also sets the mss_last_checked column to the current time
63     * to indicate that now is the last time the file was checked.
64     *
65     * If the SHA-1 of the $file does not exist in the scan table, a row
66     * will be created for it before the update occurs.
67     *
68     * @param File|ArchivedFile $file The file that was scanned by PhotoDNA
69     * @param null|bool $isMatch Whether the file is a match (null if the scan failed)
70     * @return void
71     */
72    public function updateMatchStatus( $file, ?bool $isMatch ) {
73        // Check if the SHA-1 exists in the scan table. If not, then add it to the
74        // mediamoderation_scan table first before attempting to update the match status.
75        $this->insertFileToScanTable( $file );
76        $this->updateMatchStatusForSha1( $file->getSha1(), $isMatch );
77    }
78
79    /**
80     * Updates the mediamoderation_scan row for the given SHA-1
81     * with the match status as determined by PhotoDNA.
82     *
83     * If you have a $file object, you should pass use ::updateMatchStatus
84     * instead as this method does not first check if the file is referenced
85     * in the mediamoderation_scan table.
86     *
87     * @param string $sha1
88     * @param null|bool $isMatch Whether the file is a match (null if the scan failed)
89     * @return void
90     */
91    public function updateMatchStatusForSha1( string $sha1, ?bool $isMatch ) {
92        // If $isMatch is a boolean, convert this to the string representation of a boolean
93        // for storage in the DB.
94        if ( $isMatch !== null ) {
95            $isMatch = intval( $isMatch );
96        }
97        // Update the match status for the $sha1 and also update the last checked timestamp.
98        $this->dbw->newUpdateQueryBuilder()
99            ->table( 'mediamoderation_scan' )
100            ->set( [
101                'mms_is_match' => $isMatch,
102                // Take the MW_TS current timestamp and keep the first 8 characters which is YYYYMMDD.
103                'mms_last_checked' => intval( substr( ConvertibleTimestamp::now(), 0, 8 ) )
104            ] )
105            ->where( [ 'mms_sha1' => $sha1 ] )
106            ->caller( __METHOD__ )
107            ->execute();
108    }
109}